// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2021 3snic Technologies Co., Ltd */

#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/if_vlan.h>
#include <linux/ethtool.h>
#include <linux/module.h>
#include <linux/dcbnl.h>
#include <linux/pci.h>
#include <linux/semaphore.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/ethtool.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/netdevice.h>
#include <linux/module.h>


#include "ossl_knl.h"
#include "sssnic_crm.h"
#include "sssnic_msg.h"
#include "sssnic_hw.h"
#include "sssnic_comm.h"
#include "ossl_knl.h"
#include "sssnic_crm.h"
#include "sssnic_hw.h"
#include "sssnic_msg.h"
#include "sssnic_nicq.h"
#include "sssnic_comm.h"


//sssnic_mag_cfg.c
static int mag_msg_to_mgmt_sync(void *hwdev, u16 cmd, void *buf_in, u16 in_size,
				void *buf_out, u16 *out_size);
static int mag_msg_to_mgmt_sync_ch(void *hwdev, u16 cmd, void *buf_in,
				   u16 in_size, void *buf_out, u16 *out_size,
				   u16 channel);


/*lint -e806*/
static unsigned char set_vf_link_state;
module_param(set_vf_link_state, byte, 0444);
MODULE_PARM_DESC(set_vf_link_state, "Set vf link state, 0 represents link auto, 1 represents link always up, 2 represents link always down. - default is 0.");
/*lint +e806*/

/* In order to adapt different linux version */
enum {
	SSSNIC_IFLA_VF_LINK_STATE_AUTO, /* link state of the uplink */
	SSSNIC_IFLA_VF_LINK_STATE_ENABLE, /* link always up */
	SSSNIC_IFLA_VF_LINK_STATE_DISABLE, /* link always down */
};

#define NIC_CVLAN_INSERT_ENABLE 0x1
#define NIC_QINQ_INSERT_ENBALE  0X3
static int sssnic_set_vlan_ctx(struct sssnic_nic_io *nic_io, u16 func_id,
			       u16 vlan_tag, u16 q_id, bool add)
{
	struct sssnic_vlan_ctx *vlan_ctx = NULL;
	struct ssshw_cmd_buf *cmd_buf = NULL;
	u64 out_param = 0;
	int err;

	cmd_buf = sssnic_alloc_cmd_buf(nic_io->hwdev);
	if (!cmd_buf) {
		nic_err(nic_io->dev_hdl, "Failed to allocate cmd buf\n");
		return -ENOMEM;
	}

	cmd_buf->size = sizeof(struct sssnic_vlan_ctx);
	vlan_ctx = (struct sssnic_vlan_ctx *)cmd_buf->buf;

	vlan_ctx->func_id = func_id;
	vlan_ctx->qid = q_id;
	vlan_ctx->vlan_tag = vlan_tag;
	vlan_ctx->vlan_sel = 0; /* TPID0 in IPSU */
	vlan_ctx->vlan_mode = add ?
		NIC_QINQ_INSERT_ENBALE : NIC_CVLAN_INSERT_ENABLE;

	ssshw_cpu_to_be32(vlan_ctx, sizeof(struct sssnic_vlan_ctx));

	err = ssshw_cmdq_direct_resp(nic_io->hwdev, SSSHW_MOD_L2NIC,
				      SSSNIC_UCODE_CMD_MODIFY_VLAN_CTX,
				      cmd_buf, &out_param, 0,
				      SSSHW_CHANNEL_NIC);

	ssshw_free_cmd_buf(nic_io->hwdev, cmd_buf);

	if (err || out_param != 0) {
		nic_err(nic_io->dev_hdl, "Failed to set vlan context, err: %d, out_param: 0x%llx\n",
			err, out_param);
		return -EFAULT;
	}

	return err;
}

int sssnic_cfg_vf_vlan(struct sssnic_nic_io *nic_io, u8 opcode, u16 vid,
		       u8 qos, int vf_id)
{
	struct sssnic_cmd_vf_vlan_config vf_vlan;
	u16 out_size = sizeof(vf_vlan);
	u16 glb_func_id;
	int err;
	u16 vlan_tag;

	/* VLAN 0 is a special case, don't allow it to be removed */
	if (!vid && opcode == SSSNIC_CMD_OP_DEL)
		return 0;

	memset(&vf_vlan, 0, sizeof(vf_vlan));

	vf_vlan.opcode = opcode;
	vf_vlan.func_id = ssshw_glb_pf_vf_offset(nic_io->hwdev) + (u16)vf_id;
	vf_vlan.vlan_id = vid;
	vf_vlan.qos = qos;

	err = l2nic_msg_to_mgmt_sync(nic_io->hwdev, SSSNIC_CMD_CFG_VF_VLAN,
				     &vf_vlan, sizeof(vf_vlan),
				     &vf_vlan, &out_size);
	if (err || !out_size || vf_vlan.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set VF %d vlan, err: %d, status: 0x%x,out size: 0x%x\n",
			HW_VF_ID_TO_OS(vf_id), err, vf_vlan.msg_head.status,
			out_size);
		return -EFAULT;
	}

	vlan_tag = vid + (u16)(qos << VLAN_PRIO_SHIFT);

	glb_func_id = ssshw_glb_pf_vf_offset(nic_io->hwdev) + (u16)vf_id;
	err = sssnic_set_vlan_ctx(nic_io, glb_func_id, vlan_tag,
				  NIC_CONFIG_ALL_QUEUE_VLAN_CTX,
				  opcode == SSSNIC_CMD_OP_ADD);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to set VF %d vlan ctx, err: %d\n",
			HW_VF_ID_TO_OS(vf_id), err);

		/* rollback vlan config */
		if (opcode == SSSNIC_CMD_OP_DEL)
			vf_vlan.opcode = SSSNIC_CMD_OP_ADD;
		else
			vf_vlan.opcode = SSSNIC_CMD_OP_DEL;
		l2nic_msg_to_mgmt_sync(nic_io->hwdev,
				       SSSNIC_CMD_CFG_VF_VLAN, &vf_vlan,
				       sizeof(vf_vlan), &vf_vlan, &out_size);
		return err;
	}

	return 0;
}

/*
 * this function just be called by sssnic_ndo_set_vf_mac,
 * others are not permitted.
 */
int sssnic_set_vf_mac(void *hwdev, int vf, unsigned char *mac_addr)
{
	struct vf_data_storage *vf_info;
	struct sssnic_nic_io *nic_io;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	vf_info = nic_io->vf_infos + HW_VF_ID_TO_OS(vf);
#ifndef __VMWARE__
	/* duplicate request, so just return success */
	if (ether_addr_equal(vf_info->user_mac_addr, mac_addr))
		return 0;

#else
	if (ether_addr_equal(vf_info->user_mac_addr, mac_addr))
		return 0;
#endif
	ether_addr_copy(vf_info->user_mac_addr, mac_addr);

	return 0;
}

int sssnic_add_vf_vlan(void *hwdev, int vf_id, u16 vlan, u8 qos)
{
	struct sssnic_nic_io *nic_io;
	int err;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	err = sssnic_cfg_vf_vlan(nic_io, SSSNIC_CMD_OP_ADD, vlan, qos, vf_id);
	if (err)
		return err;

	nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_vlan = vlan;
	nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_qos = qos;

	nic_info(nic_io->dev_hdl, "Setting VLAN %u, QOS 0x%x on VF %d\n",
		 vlan, qos, HW_VF_ID_TO_OS(vf_id));

	return 0;
}

int sssnic_kill_vf_vlan(void *hwdev, int vf_id)
{
	struct vf_data_storage *vf_infos;
	struct sssnic_nic_io *nic_io;
	int err;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	vf_infos = nic_io->vf_infos;

	err = sssnic_cfg_vf_vlan(nic_io, SSSNIC_CMD_OP_DEL,
				 vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_vlan,
				 vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_qos, vf_id);
	if (err)
		return err;

	nic_info(nic_io->dev_hdl, "Remove VLAN %u on VF %d\n",
		 vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_vlan,
		 HW_VF_ID_TO_OS(vf_id));

	vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_vlan = 0;
	vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_qos = 0;

	return 0;
}

u16 sssnic_vf_info_vlanprio(void *hwdev, int vf_id)
{
	struct sssnic_nic_io *nic_io;
	u16 pf_vlan, vlanprio;
	u8 pf_qos;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	pf_vlan = nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_vlan;
	pf_qos = nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].pf_qos;
	vlanprio = (u16)(pf_vlan | pf_qos << SSSNIC_VLAN_PRIORITY_SHIFT);

	return vlanprio;
}

int sssnic_set_vf_link_state(void *hwdev, u16 vf_id, int link)
{
	struct sssnic_nic_io *nic_io =
		sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	struct vf_data_storage *vf_infos = nic_io->vf_infos;
	u8 link_status = 0;

	switch (link) {
	case SSSNIC_IFLA_VF_LINK_STATE_AUTO:
		vf_infos[HW_VF_ID_TO_OS(vf_id)].link_forced = false;
		vf_infos[HW_VF_ID_TO_OS(vf_id)].link_up = nic_io->link_status ?
			true : false;
		link_status = nic_io->link_status;
		break;
	case SSSNIC_IFLA_VF_LINK_STATE_ENABLE:
		vf_infos[HW_VF_ID_TO_OS(vf_id)].link_forced = true;
		vf_infos[HW_VF_ID_TO_OS(vf_id)].link_up = true;
		link_status = SSSNIC_LINK_UP;
		break;
	case SSSNIC_IFLA_VF_LINK_STATE_DISABLE:
		vf_infos[HW_VF_ID_TO_OS(vf_id)].link_forced = true;
		vf_infos[HW_VF_ID_TO_OS(vf_id)].link_up = false;
		link_status = SSSNIC_LINK_DOWN;
		break;
	default:
		return -EINVAL;
	}

	/* Notify the VF of its new link state */
	sssnic_notify_vf_link_status(nic_io, vf_id, link_status);

	return 0;
}

int sssnic_set_vf_spoofchk(void *hwdev, u16 vf_id, bool spoofchk)
{
	struct sssnic_cmd_spoofchk_set spoofchk_cfg;
	struct vf_data_storage *vf_infos = NULL;
	u16 out_size = sizeof(spoofchk_cfg);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	vf_infos = nic_io->vf_infos;

	memset(&spoofchk_cfg, 0, sizeof(spoofchk_cfg));

	spoofchk_cfg.func_id = ssshw_glb_pf_vf_offset(hwdev) + vf_id;
	spoofchk_cfg.state = spoofchk ? 1 : 0;
	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_SET_SPOOPCHK_STATE,
				     &spoofchk_cfg,
				     sizeof(spoofchk_cfg), &spoofchk_cfg,
				     &out_size);
	if (err || !out_size || spoofchk_cfg.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set VF(%d) spoofchk, err: %d, status: 0x%x, out size: 0x%x\n",
			HW_VF_ID_TO_OS(vf_id), err,
			spoofchk_cfg.msg_head.status, out_size);
		err = -EINVAL;
	}

	vf_infos[HW_VF_ID_TO_OS(vf_id)].spoofchk = spoofchk;

	return err;
}

bool sssnic_vf_info_spoofchk(void *hwdev, int vf_id)
{
	struct sssnic_nic_io *nic_io;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	return nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].spoofchk;
}

#ifdef HAVE_NDO_SET_VF_TRUST
int sssnic_set_vf_trust(void *hwdev, u16 vf_id, bool trust)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (vf_id > nic_io->max_vfs)
		return -EINVAL;

	nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].trust = trust;

	return 0;
}

bool sssnic_get_vf_trust(void *hwdev, int vf_id)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (vf_id > nic_io->max_vfs)
		return -EINVAL;

	return nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].trust;
}
#endif

static int sssnic_set_vf_tx_rate_max_min(struct sssnic_nic_io *nic_io,
					 u16 vf_id, u32 max_rate, u32 min_rate)
{
	struct sssnic_cmd_tx_rate_cfg rate_cfg;
	u16 out_size = sizeof(rate_cfg);
	int err;

	memset(&rate_cfg, 0, sizeof(rate_cfg));

	rate_cfg.func_id = ssshw_glb_pf_vf_offset(nic_io->hwdev) + vf_id;
	rate_cfg.max_rate = max_rate;
	rate_cfg.min_rate = min_rate;
	err = l2nic_msg_to_mgmt_sync(nic_io->hwdev,
				     SSSNIC_CMD_SET_MAX_MIN_RATE,
				     &rate_cfg, sizeof(rate_cfg), &rate_cfg,
				     &out_size);
	if (rate_cfg.msg_head.status || err || !out_size) {
		nic_err(nic_io->dev_hdl, "Failed to set VF %d max rate %u, min rate %u, err: %d, status: 0x%x, out size: 0x%x\n",
			HW_VF_ID_TO_OS(vf_id), max_rate, min_rate, err,
			rate_cfg.msg_head.status, out_size);
		return -EIO;
	}

	return 0;
}

int sssnic_set_vf_tx_rate(void *hwdev, u16 vf_id, u32 max_rate, u32 min_rate)
{
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	err = sssnic_set_vf_tx_rate_max_min(nic_io, vf_id, max_rate, min_rate);
	if (err)
		return err;

	nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].max_rate = max_rate;
	nic_io->vf_infos[HW_VF_ID_TO_OS(vf_id)].min_rate = min_rate;

	return 0;
}

void sssnic_get_vf_config(void *hwdev, u16 vf_id, struct ifla_vf_info *ivi)
{
	struct vf_data_storage *vfinfo;
	struct sssnic_nic_io *nic_io;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	vfinfo = nic_io->vf_infos + HW_VF_ID_TO_OS(vf_id);

	ivi->vf = HW_VF_ID_TO_OS(vf_id);
	ether_addr_copy(ivi->mac, vfinfo->user_mac_addr);
	ivi->vlan = vfinfo->pf_vlan;
	ivi->qos = vfinfo->pf_qos;

#ifdef HAVE_VF_SPOOFCHK_CONFIGURE
	ivi->spoofchk = vfinfo->spoofchk;
#endif

#ifdef HAVE_NDO_SET_VF_TRUST
	ivi->trusted = vfinfo->trust;
#endif

#ifdef HAVE_NDO_SET_VF_MIN_MAX_TX_RATE
	ivi->max_tx_rate = vfinfo->max_rate;
	ivi->min_tx_rate = vfinfo->min_rate;
#else
	ivi->tx_rate = vfinfo->max_rate;
#endif /* HAVE_NDO_SET_VF_MIN_MAX_TX_RATE */

#ifdef HAVE_NDO_SET_VF_LINK_STATE
	if (!vfinfo->link_forced)
		ivi->linkstate = IFLA_VF_LINK_STATE_AUTO;
	else if (vfinfo->link_up)
		ivi->linkstate = IFLA_VF_LINK_STATE_ENABLE;
	else
		ivi->linkstate = IFLA_VF_LINK_STATE_DISABLE;
#endif
}

static int sssnic_init_vf_infos(struct sssnic_nic_io *nic_io, u16 vf_id)
{
	struct vf_data_storage *vf_infos = nic_io->vf_infos;
	u8 vf_link_state;

	if (set_vf_link_state > SSSNIC_IFLA_VF_LINK_STATE_DISABLE) {
		nic_warn(nic_io->dev_hdl, "Module Parameter set_vf_link_state value %u is out of range, resetting to %d\n",
			 set_vf_link_state, SSSNIC_IFLA_VF_LINK_STATE_AUTO);
		set_vf_link_state = SSSNIC_IFLA_VF_LINK_STATE_AUTO;
	}

	vf_link_state = set_vf_link_state;

	switch (vf_link_state) {
	case SSSNIC_IFLA_VF_LINK_STATE_AUTO:
		vf_infos[vf_id].link_forced = false;
		break;
	case SSSNIC_IFLA_VF_LINK_STATE_ENABLE:
		vf_infos[vf_id].link_forced = true;
		vf_infos[vf_id].link_up = true;
		break;
	case SSSNIC_IFLA_VF_LINK_STATE_DISABLE:
		vf_infos[vf_id].link_forced = true;
		vf_infos[vf_id].link_up = false;
		break;
	default:
		nic_err(nic_io->dev_hdl, "Input parameter set_vf_link_state error: %u\n",
			vf_link_state);
		return -EINVAL;
	}

	return 0;
}

static int vf_func_register(struct sssnic_nic_io *nic_io)
{
	struct sssnic_cmd_register_vf register_info;
	u16 out_size = sizeof(register_info);
	int err;

	err = ssshw_register_vf_mbox_cb(nic_io->hwdev, SSSHW_MOD_L2NIC,
					 nic_io->hwdev, sssnic_vf_event_handler);
	if (err)
		return err;

	err = ssshw_register_vf_mbox_cb(nic_io->hwdev, SSSHW_MOD_SSSLINK,
					 nic_io->hwdev, sssnic_vf_mag_event_handler);
	if (err)
		goto reg_ssslink_err;

	memset(&register_info, 0, sizeof(register_info));
	register_info.op_register = 1;
	register_info.support_extra_feature = 0;
	err = ssshw_mbox_to_pf(nic_io->hwdev, SSSHW_MOD_L2NIC,
				SSSNIC_CMD_VF_REGISTER,
				&register_info, sizeof(register_info),
				&register_info, &out_size, 0,
				SSSHW_CHANNEL_NIC);
	if (err || !out_size || register_info.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to register VF, err: %d, status: 0x%x, out size: 0x%x\n",
			err, register_info.msg_head.status, out_size);
		err = -EIO;
		goto register_err;
	}

	return 0;

register_err:
	ssshw_unregister_vf_mbox_cb(nic_io->hwdev, SSSHW_MOD_SSSLINK);

reg_ssslink_err:
	ssshw_unregister_vf_mbox_cb(nic_io->hwdev, SSSHW_MOD_L2NIC);

	return err;
}

static int pf_init_vf_infos(struct sssnic_nic_io *nic_io)
{
	u32 size;
	int err;
	u16 i;

	nic_io->max_vfs = ssshw_func_max_vf(nic_io->hwdev);
	size = sizeof(*nic_io->vf_infos) * nic_io->max_vfs;
	if (!size)
		return 0;

	nic_io->vf_infos = kzalloc(size, GFP_KERNEL);
	if (!nic_io->vf_infos)
		return -ENOMEM;

	for (i = 0; i < nic_io->max_vfs; i++) {
		err = sssnic_init_vf_infos(nic_io, i);
		if (err)
			goto init_vf_infos_err;
	}

	err = ssshw_register_pf_mbox_cb(nic_io->hwdev, SSSHW_MOD_L2NIC,
					 nic_io->hwdev, sssnic_pf_mbox_handler);
	if (err)
		goto register_pf_mbox_cb_err;

	err = ssshw_register_pf_mbox_cb(nic_io->hwdev, SSSHW_MOD_SSSLINK,
					 nic_io->hwdev, sssnic_pf_mag_mbox_handler);
	if (err)
		goto register_pf_mag_mbox_cb_err;

	return 0;

register_pf_mag_mbox_cb_err:
	ssshw_unregister_pf_mbox_cb(nic_io->hwdev, SSSHW_MOD_L2NIC);
register_pf_mbox_cb_err:
init_vf_infos_err:
	kfree(nic_io->vf_infos);

	return err;
}

int sssnic_vf_func_init(struct sssnic_nic_io *nic_io)
{
	int err;

	if (ssshw_func_type(nic_io->hwdev) == SSSHW_TYPE_VF)
		return vf_func_register(nic_io);

	err = ssshw_register_mgmt_msg_cb(nic_io->hwdev, SSSHW_MOD_L2NIC,
					  nic_io->hwdev, sssnic_pf_event_handler);
	if (err)
		return err;

	err = ssshw_register_mgmt_msg_cb(nic_io->hwdev, SSSHW_MOD_SSSLINK,
					  nic_io->hwdev, sssnic_pf_mag_event_handler);
	if (err)
		goto register_mgmt_msg_cb_err;

	err = pf_init_vf_infos(nic_io);
	if (err)
		goto pf_init_vf_infos_err;

	return 0;

pf_init_vf_infos_err:
	ssshw_unregister_mgmt_msg_cb(nic_io->hwdev, SSSHW_MOD_SSSLINK);
register_mgmt_msg_cb_err:
	ssshw_unregister_mgmt_msg_cb(nic_io->hwdev, SSSHW_MOD_L2NIC);

	return err;
}

void sssnic_vf_func_free(struct sssnic_nic_io *nic_io)
{
	struct sssnic_cmd_register_vf unregister;
	u16 out_size = sizeof(unregister);
	int err;

	memset(&unregister, 0, sizeof(unregister));
	unregister.op_register = 0;
	if (ssshw_func_type(nic_io->hwdev) == SSSHW_TYPE_VF) {
		err = ssshw_mbox_to_pf(nic_io->hwdev, SSSHW_MOD_L2NIC,
					SSSNIC_CMD_VF_REGISTER,
					&unregister, sizeof(unregister),
					&unregister, &out_size, 0,
					SSSHW_CHANNEL_NIC);
		if (err || !out_size || unregister.msg_head.status)
			nic_err(nic_io->dev_hdl, "Failed to unregister VF, err: %d, status: 0x%x, out_size: 0x%x\n",
				err, unregister.msg_head.status, out_size);

		ssshw_unregister_vf_mbox_cb(nic_io->hwdev, SSSHW_MOD_L2NIC);
	} else {
		if (nic_io->vf_infos) {
			ssshw_unregister_pf_mbox_cb(nic_io->hwdev, SSSHW_MOD_SSSLINK);
			ssshw_unregister_pf_mbox_cb(nic_io->hwdev, SSSHW_MOD_L2NIC);
			sssnic_clear_vfs_info(nic_io->hwdev);
			kfree(nic_io->vf_infos);
		}
		ssshw_unregister_mgmt_msg_cb(nic_io->hwdev, SSSHW_MOD_SSSLINK);
		ssshw_unregister_mgmt_msg_cb(nic_io->hwdev, SSSHW_MOD_L2NIC);
	}
}

static void clear_vf_infos(void *hwdev, u16 vf_id)
{
	struct vf_data_storage *vf_infos;
	struct sssnic_nic_io *nic_io;
	u16 func_id;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	func_id = ssshw_glb_pf_vf_offset(hwdev) + vf_id;
	vf_infos = nic_io->vf_infos + HW_VF_ID_TO_OS(vf_id);
	if (vf_infos->use_specified_mac)
		sssnic_del_mac(hwdev, vf_infos->drv_mac_addr,
			       vf_infos->pf_vlan, func_id, SSSHW_CHANNEL_NIC);

	if (sssnic_vf_info_vlanprio(hwdev, vf_id))
		sssnic_kill_vf_vlan(hwdev, vf_id);

	if (vf_infos->max_rate)
		sssnic_set_vf_tx_rate(hwdev, vf_id, 0, 0);

	if (vf_infos->spoofchk)
		sssnic_set_vf_spoofchk(hwdev, vf_id, false);

#ifdef HAVE_NDO_SET_VF_TRUST
	if (vf_infos->trust)
		sssnic_set_vf_trust(hwdev, vf_id, false);
#endif

	memset(vf_infos, 0, sizeof(*vf_infos));
	/* set vf_infos to default */
	sssnic_init_vf_infos(nic_io, HW_VF_ID_TO_OS(vf_id));
}

void sssnic_clear_vfs_info(void *hwdev)
{
	struct sssnic_nic_io *nic_io =
			sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	u16 i;

	for (i = 0; i < nic_io->max_vfs; i++)
		clear_vf_infos(hwdev, OS_VF_ID_TO_HW(i));
}


static int sssnic_cfg_loopback_mode(struct sssnic_nic_io *nic_io, u8 opcode,
				    u8 *mode, u8 *enable)
{
	struct mag_cmd_cfg_loopback_mode lp;
	u16 out_size = sizeof(lp);
	int err;

	memset(&lp, 0, sizeof(lp));
	lp.port_id = ssshw_physical_port_id(nic_io->hwdev);
	lp.opcode = opcode;
	if (opcode == SSSHW_MGMT_MSG_CMD_OP_SET) {
		lp.lp_mode = *mode;
		lp.lp_en = *enable;
	}

	err = mag_msg_to_mgmt_sync(nic_io->hwdev, MAG_CMD_CFG_LOOPBACK_MODE,
				   &lp, sizeof(lp), &lp, &out_size);
	if (err || !out_size || lp.head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to %s loopback mode, err: %d, status: 0x%x, out size: 0x%x\n",
			opcode == SSSHW_MGMT_MSG_CMD_OP_SET ? "set" : "get",
			err, lp.head.status, out_size);
		return -EIO;
	}

	if (opcode == SSSHW_MGMT_MSG_CMD_OP_GET) {
		*mode = lp.lp_mode;
		*enable = lp.lp_en;
	}

	return 0;
}

int sssnic_get_loopback_mode(void *hwdev, u8 *mode, u8 *enable)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev || !mode || !enable)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	return sssnic_cfg_loopback_mode(nic_io, SSSHW_MGMT_MSG_CMD_OP_GET, mode,
					enable);
}

#define LOOP_MODE_MIN 1
#define LOOP_MODE_MAX 6
int sssnic_set_loopback_mode(void *hwdev, u8 mode, u8 enable)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	if (mode < LOOP_MODE_MIN || mode > LOOP_MODE_MAX) {
		nic_err(nic_io->dev_hdl, "Invalid loopback mode %u to set\n",
			mode);
		return -EINVAL;
	}

	return sssnic_cfg_loopback_mode(nic_io, SSSHW_MGMT_MSG_CMD_OP_SET, &mode,
					&enable);
}

int sssnic_set_led_status(void *hwdev, enum mag_led_type type,
			  enum mag_led_mode mode)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct mag_cmd_set_led_cfg led_info;
	u16 out_size = sizeof(led_info);
	int err;

	if (!hwdev)
		return -EFAULT;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memset(&led_info, 0, sizeof(led_info));

	led_info.function_id = ssshw_global_func_id(hwdev);
	led_info.type = type;
	led_info.mode = mode;

	err = mag_msg_to_mgmt_sync(hwdev, MAG_CMD_SET_LED_CFG, &led_info,
				   sizeof(led_info), &led_info, &out_size);
	if (err || led_info.head.status || !out_size) {
		nic_err(nic_io->dev_hdl, "Failed to set led status, err: %d, status: 0x%x, out size: 0x%x\n",
			err, led_info.head.status, out_size);
		return -EIO;
	}

	return 0;
}

int sssnic_get_port_info(void *hwdev, struct sssnic_port_info *port_info,
			 u16 channel)
{
	struct mag_cmd_get_port_info port_msg;
	u16 out_size = sizeof(port_msg);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !port_info)
		return -EINVAL;

	memset(&port_msg, 0, sizeof(port_msg));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	port_msg.port_id = ssshw_physical_port_id(hwdev);

	err = mag_msg_to_mgmt_sync_ch(hwdev, MAG_CMD_GET_PORT_INFO, &port_msg,
				      sizeof(port_msg), &port_msg, &out_size,
				      channel);
	if (err || !out_size || port_msg.head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to get port info, err: %d, status: 0x%x, out size: 0x%x, channel: 0x%x\n",
			err, port_msg.head.status, out_size, channel);
		return -EIO;
	}

	port_info->autoneg_cap = port_msg.an_support;
	port_info->autoneg_state = port_msg.an_en;
	port_info->duplex = port_msg.duplex;
	port_info->port_type = port_msg.wire_type;
	port_info->speed = port_msg.speed;
	port_info->fec = port_msg.fec;
	port_info->supported_mode = port_msg.supported_mode;
	port_info->advertised_mode = port_msg.advertised_mode;

	return 0;
}

int sssnic_get_speed(void *hwdev, enum mag_cmd_port_speed *speed, u16 channel)
{
	struct sssnic_port_info port_info = {0};
	int err;

	if (!hwdev || !speed)
		return -EINVAL;

	err = sssnic_get_port_info(hwdev, &port_info, channel);
	if (err)
		return err;

	*speed = port_info.speed;

	return 0;
}
EXPORT_SYMBOL(sssnic_get_speed);

int sssnic_set_link_settings(void *hwdev,
			     struct sssnic_link_ksettings *settings)
{
	struct mag_cmd_set_port_cfg info;
	u16 out_size = sizeof(info);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !settings)
		return -EINVAL;

	memset(&info, 0, sizeof(info));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	info.port_id = ssshw_physical_port_id(hwdev);
	info.config_bitmap = settings->valid_bitmap;
	info.autoneg = settings->autoneg;
	info.speed = settings->speed;
	info.fec = settings->fec;

	err = mag_msg_to_mgmt_sync(hwdev, MAG_CMD_SET_PORT_CFG, &info,
				   sizeof(info), &info, &out_size);
	if (err || !out_size || info.head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set link settings, err: %d, status: 0x%x, out size: 0x%x\n",
			err, info.head.status, out_size);
		return -EIO;
	}

	return info.head.status;
}

int sssnic_get_link_state(void *hwdev, u8 *link_state)
{
	struct mag_cmd_get_link_status get_link;
	u16 out_size = sizeof(get_link);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !link_state)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&get_link, 0, sizeof(get_link));
	get_link.port_id = ssshw_physical_port_id(hwdev);

	err = mag_msg_to_mgmt_sync(hwdev, MAG_CMD_GET_LINK_STATUS, &get_link,
				   sizeof(get_link), &get_link, &out_size);
	if (err || !out_size || get_link.head.status) {
		nic_err(nic_io->dev_hdl, "Failed to get link state, err: %d, status: 0x%x, out size: 0x%x\n",
			err, get_link.head.status, out_size);
		return -EIO;
	}

	*link_state = get_link.status;

	return 0;
}

void sssnic_notify_vf_link_status(struct sssnic_nic_io *nic_io,
				  u16 vf_id, u8 link_status)
{
	struct mag_cmd_get_link_status link;
	struct vf_data_storage *vf_infos = nic_io->vf_infos;
	u16 out_size = sizeof(link);
	int err;

	memset(&link, 0, sizeof(link));
	if (vf_infos[HW_VF_ID_TO_OS(vf_id)].registered) {
		link.status = link_status;
		link.port_id = ssshw_physical_port_id(nic_io->hwdev);
		err = ssshw_mbox_to_vf(nic_io->hwdev, vf_id, SSSHW_MOD_SSSLINK,
					MAG_CMD_GET_LINK_STATUS, &link,
					sizeof(link), &link, &out_size, 0,
					SSSHW_CHANNEL_NIC);
		if (err || !out_size || link.head.status)
			nic_err(nic_io->dev_hdl,
				"Send link change event to VF %d failed, err: %d, status: 0x%x, out_size: 0x%x\n",
				HW_VF_ID_TO_OS(vf_id), err,
				link.head.status, out_size);
	}
}

void sssnic_notify_all_vfs_link_changed(void *hwdev, u8 link_status)
{
	struct sssnic_nic_io *nic_io = NULL;
	u16 i;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	nic_io->link_status = link_status;
	for (i = 1; i <= nic_io->max_vfs; i++) {
		if (!nic_io->vf_infos[HW_VF_ID_TO_OS(i)].link_forced)
			sssnic_notify_vf_link_status(nic_io, i, link_status);
	}
}

static int sssnic_get_vf_link_status_msg_handler(struct sssnic_nic_io *nic_io,
						 u16 vf_id, void *buf_in,
						 u16 in_size, void *buf_out,
						 u16 *out_size)
{
	struct vf_data_storage *vf_infos = nic_io->vf_infos;
	struct mag_cmd_get_link_status *get_link = buf_out;
	bool link_forced, link_up;

	link_forced = vf_infos[HW_VF_ID_TO_OS(vf_id)].link_forced;
	link_up = vf_infos[HW_VF_ID_TO_OS(vf_id)].link_up;

	if (link_forced)
		get_link->status = link_up ?
					SSSNIC_LINK_UP : SSSNIC_LINK_DOWN;
	else
		get_link->status = nic_io->link_status;

	get_link->head.status = 0;
	*out_size = sizeof(*get_link);

	return 0;
}

int sssnic_refresh_nic_cfg(void *hwdev, struct sssnic_port_info *port_info)
{
	/*TO DO */
	return 0;
}

static void get_port_info(void *hwdev,
			  const struct mag_cmd_get_link_status *link_status,
			  struct sssnic_event_link_info *link_info)
{
	struct sssnic_port_info port_info = {0};
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (ssshw_func_type(hwdev) != SSSHW_TYPE_VF && link_status->status) {
		err = sssnic_get_port_info(hwdev, &port_info, SSSHW_CHANNEL_NIC);
		if (err) {
			nic_warn(nic_io->dev_hdl, "Failed to get port info\n");
		} else {
			link_info->valid = 1;
			link_info->port_type = port_info.port_type;
			link_info->autoneg_cap = port_info.autoneg_cap;
			link_info->autoneg_state = port_info.autoneg_state;
			link_info->duplex = port_info.duplex;
			link_info->speed = port_info.speed;
			sssnic_refresh_nic_cfg(hwdev, &port_info);
		}
	}
}

static void link_status_event_handler(void *hwdev, void *buf_in,
				      u16 in_size, void *buf_out, u16 *out_size)
{
	struct mag_cmd_get_link_status *link_status = NULL;
	struct mag_cmd_get_link_status *ret_link_status = NULL;
	struct ssshw_event_info event_info = {0};
	struct sssnic_event_link_info *link_info = (void *)event_info.event_data;
	struct sssnic_nic_io *nic_io = NULL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	link_status = buf_in;
	sdk_info(nic_io->dev_hdl, "Link status report received, func_id: %u, status: %u\n",
		 ssshw_global_func_id(hwdev), link_status->status);

	ssshw_link_event_stats(hwdev, link_status->status);

	/* link event reported only after set vport enable */
	get_port_info(hwdev, link_status, link_info);

	event_info.service = SSSHW_EVENT_SRV_NIC;
	event_info.type = link_status->status ?
				EVENT_NIC_LINK_UP : EVENT_NIC_LINK_DOWN;

	ssshw_event_callback(hwdev, &event_info);

	if (ssshw_func_type(hwdev) != SSSHW_TYPE_VF) {
		sssnic_notify_all_vfs_link_changed(hwdev, link_status->status);
		ret_link_status = buf_out;
		ret_link_status->head.status = 0;
		*out_size = sizeof(*ret_link_status);
	}
}

static void cable_plug_event(void *hwdev, void *buf_in, u16 in_size,
			     void *buf_out, u16 *out_size)
{
	struct mag_cmd_wire_event *plug_event = buf_in;
	struct sssnic_port_routine_cmd *rt_cmd = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	struct ssshw_event_info event_info;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	rt_cmd = &nic_io->nic_cfg.rt_cmd;

	mutex_lock(&nic_io->nic_cfg.sfp_mutex);
	rt_cmd->mpu_send_sfp_abs = false;
	rt_cmd->mpu_send_sfp_info = false;
	mutex_unlock(&nic_io->nic_cfg.sfp_mutex);

	memset(&event_info, 0, sizeof(event_info));
	event_info.service = SSSHW_EVENT_SRV_NIC;
	event_info.type = EVENT_NIC_PORT_MODULE_EVENT;
	((struct sssnic_port_module_event *)(void *)event_info.event_data)->type =
				plug_event->status ? SSSNIC_PORT_MODULE_CABLE_PLUGGED :
				SSSNIC_PORT_MODULE_CABLE_UNPLUGGED;

	*out_size = sizeof(*plug_event);
	plug_event = buf_out;
	plug_event->head.status = 0;

	ssshw_event_callback(hwdev, &event_info);
}

static void port_sfp_info_event(void *hwdev, void *buf_in, u16 in_size,
				void *buf_out, u16 *out_size)
{
	struct mag_cmd_get_xsfp_info *sfp_info = buf_in;
	struct sssnic_port_routine_cmd *rt_cmd = NULL;
	struct sssnic_nic_io *nic_io = NULL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (in_size != sizeof(*sfp_info)) {
		sdk_err(nic_io->dev_hdl, "Invalid sfp info cmd, length: %u, should be %ld\n",
			in_size, sizeof(*sfp_info));
		return;
	}

	rt_cmd = &nic_io->nic_cfg.rt_cmd;
	mutex_lock(&nic_io->nic_cfg.sfp_mutex);
	memcpy(&rt_cmd->std_sfp_info, sfp_info,
	       sizeof(struct mag_cmd_get_xsfp_info));
	rt_cmd->mpu_send_sfp_info = true;
	mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
}

static void port_sfp_abs_event(void *hwdev, void *buf_in, u16 in_size,
			       void *buf_out, u16 *out_size)
{
	struct mag_cmd_get_xsfp_present *sfp_abs = buf_in;
	struct sssnic_port_routine_cmd *rt_cmd = NULL;
	struct sssnic_nic_io *nic_io = NULL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (in_size != sizeof(*sfp_abs)) {
		sdk_err(nic_io->dev_hdl, "Invalid sfp absent cmd, length: %u, should be %ld\n",
			in_size, sizeof(*sfp_abs));
		return;
	}

	rt_cmd = &nic_io->nic_cfg.rt_cmd;
	mutex_lock(&nic_io->nic_cfg.sfp_mutex);
	memcpy(&rt_cmd->abs, sfp_abs,
	       sizeof(struct mag_cmd_get_xsfp_present));
	rt_cmd->mpu_send_sfp_abs = true;
	mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
}

bool sssnic_if_sfp_absent(void *hwdev)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_port_routine_cmd *rt_cmd = NULL;
	struct mag_cmd_get_xsfp_present sfp_abs;
	u8 port_id = ssshw_physical_port_id(hwdev);
	u16 out_size = sizeof(sfp_abs);
	int err;
	bool sfp_abs_status;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memset(&sfp_abs, 0, sizeof(sfp_abs));

	rt_cmd = &nic_io->nic_cfg.rt_cmd;
	mutex_lock(&nic_io->nic_cfg.sfp_mutex);
	if (rt_cmd->mpu_send_sfp_abs) {
		if (rt_cmd->abs.head.status) {
			mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
			return true;
		}

		sfp_abs_status = (bool)rt_cmd->abs.abs_status;
		mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
		return sfp_abs_status;
	}
	mutex_unlock(&nic_io->nic_cfg.sfp_mutex);

	sfp_abs.port_id = port_id;
	err = mag_msg_to_mgmt_sync(hwdev, MAG_CMD_GET_XSFP_PRESENT,
				   &sfp_abs, sizeof(sfp_abs), &sfp_abs,
				   &out_size);
	if (sfp_abs.head.status || err || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Failed to get port%u sfp absent status, err: %d, status: 0x%x, out size: 0x%x\n",
			port_id, err, sfp_abs.head.status, out_size);
		return true;
	}

	return (sfp_abs.abs_status == 0 ? false : true);
}

int sssnic_get_sfp_info(void *hwdev, struct mag_cmd_get_xsfp_info *sfp_info)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_port_routine_cmd *rt_cmd = NULL;
	u16 out_size = sizeof(*sfp_info);
	int err;

	if (!hwdev || !sfp_info)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	rt_cmd = &nic_io->nic_cfg.rt_cmd;
	mutex_lock(&nic_io->nic_cfg.sfp_mutex);
	if (rt_cmd->mpu_send_sfp_info) {
		if (rt_cmd->std_sfp_info.head.status) {
			mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
			return -EIO;
		}

		memcpy(sfp_info, &rt_cmd->std_sfp_info, sizeof(*sfp_info));
		mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
		return 0;
	}
	mutex_unlock(&nic_io->nic_cfg.sfp_mutex);

	sfp_info->port_id = ssshw_physical_port_id(hwdev);
	err = mag_msg_to_mgmt_sync(hwdev, MAG_CMD_GET_XSFP_INFO, sfp_info,
				   sizeof(*sfp_info), sfp_info, &out_size);
	if (sfp_info->head.status || err || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Failed to get port%u sfp eeprom information, err: %d, status: 0x%x, out size: 0x%x\n",
			ssshw_physical_port_id(hwdev), err,
			sfp_info->head.status, out_size);
		return -EIO;
	}

	return 0;
}

int sssnic_get_sfp_eeprom(void *hwdev, u8 *data, u32 len)
{
	struct mag_cmd_get_xsfp_info sfp_info;
	int err;

	if (!hwdev || !data)
		return -EINVAL;

	if (sssnic_if_sfp_absent(hwdev))
		return -ENXIO;

	memset(&sfp_info, 0, sizeof(sfp_info));

	err = sssnic_get_sfp_info(hwdev, &sfp_info);
	if (err)
		return err;

	memcpy(data, sfp_info.sfp_info, len);

	return  0;
}

int sssnic_get_sfp_type(void *hwdev, u8 *sfp_type, u8 *sfp_type_ext)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_port_routine_cmd *rt_cmd = NULL;
	u8 sfp_data[STD_SFP_INFO_MAX_SIZE];
	int err;

	if (!hwdev || !sfp_type || !sfp_type_ext)
		return -EINVAL;

	if (sssnic_if_sfp_absent(hwdev))
		return -ENXIO;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	rt_cmd = &nic_io->nic_cfg.rt_cmd;

	mutex_lock(&nic_io->nic_cfg.sfp_mutex);
	if (rt_cmd->mpu_send_sfp_info) {
		if (rt_cmd->std_sfp_info.head.status) {
			mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
			return -EIO;
		}

		*sfp_type = rt_cmd->std_sfp_info.sfp_info[0];
		*sfp_type_ext = rt_cmd->std_sfp_info.sfp_info[1];
		mutex_unlock(&nic_io->nic_cfg.sfp_mutex);
		return 0;
	}
	mutex_unlock(&nic_io->nic_cfg.sfp_mutex);

	err = sssnic_get_sfp_eeprom(hwdev, (u8 *)sfp_data,
				    STD_SFP_INFO_MAX_SIZE);
	if (err)
		return err;

	*sfp_type = sfp_data[0];
	*sfp_type_ext = sfp_data[1];

	return 0;
}

int sssnic_set_link_status_follow(void *hwdev, enum sssnic_link_follow_status status)
{
	struct mag_cmd_set_link_follow follow;
	struct sssnic_nic_io *nic_io = NULL;
	u16 out_size = sizeof(follow);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	if (status >= SSSNIC_LINK_FOLLOW_STATUS_MAX) {
		nic_err(nic_io->dev_hdl, "Invalid link follow status: %d\n", status);
		return -EINVAL;
	}

	memset(&follow, 0, sizeof(follow));
	follow.function_id = ssshw_global_func_id(hwdev);
	follow.follow = status;

	err = mag_msg_to_mgmt_sync(hwdev, MAG_CMD_SET_LINK_FOLLOW, &follow,
				   sizeof(follow), &follow, &out_size);
	if ((follow.head.status != SSSHW_MGMT_CMD_UNSUPPORTED && follow.head.status) ||
	    err || !out_size) {
		nic_err(nic_io->dev_hdl, "Failed to set link status follow port status, err: %d, status: 0x%x, out size: 0x%x\n",
			err, follow.head.status, out_size);
		return -EFAULT;
	}

	return follow.head.status;
}

int sssnic_update_pf_bw(void *hwdev)
{
	struct sssnic_port_info port_info = {0};
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (ssshw_func_type(hwdev) == SSSHW_TYPE_VF || !SSSNIC_SUPPORT_RATE_LIMIT(hwdev))
		return 0;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	err = sssnic_get_port_info(hwdev, &port_info, SSSHW_CHANNEL_NIC);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to get port info\n");
		return -EIO;
	}

	err = sssnic_set_pf_rate(hwdev, port_info.speed);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to set pf bandwidth\n");
		return err;
	}

	return 0;
}

int sssnic_set_pf_bw_limit(void *hwdev, u32 bw_limit)
{
	struct sssnic_nic_io *nic_io = NULL;
	u32 old_bw_limit;
	u8 link_state = 0;
	int err;

	if (!hwdev)
		return -EINVAL;

	if (ssshw_func_type(hwdev) == SSSHW_TYPE_VF)
		return 0;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	if (bw_limit > MAX_LIMIT_BW) {
		nic_err(nic_io->dev_hdl, "Invalid bandwidth: %u\n", bw_limit);
		return -EINVAL;
	}

	err = sssnic_get_link_state(hwdev, &link_state);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to get link state\n");
		return -EIO;
	}

	if (!link_state) {
		nic_err(nic_io->dev_hdl, "Link status must be up when setting pf tx rate\n");
		return -EINVAL;
	}

	old_bw_limit = nic_io->nic_cfg.pf_bw_limit;
	nic_io->nic_cfg.pf_bw_limit = bw_limit;

	err = sssnic_update_pf_bw(hwdev);
	if (err) {
		nic_io->nic_cfg.pf_bw_limit = old_bw_limit;
		return err;
	}

	return 0;
}

static const struct vf_msg_handler vf_mag_cmd_handler[] = {
	{
		.cmd = MAG_CMD_GET_LINK_STATUS,
		.handler = sssnic_get_vf_link_status_msg_handler,
	},
};

/* pf/ppf handler mbox msg from vf */
int sssnic_pf_mag_mbox_handler(void *hwdev, u16 vf_id,
			       u16 cmd, void *buf_in, u16 in_size,
			       void *buf_out, u16 *out_size)
{
	u32 index, cmd_size = SSSHW_ARRAY_LEN(vf_mag_cmd_handler);
	struct sssnic_nic_io *nic_io = NULL;
	const struct vf_msg_handler *handler = NULL;

	if (!hwdev)
		return -EFAULT;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	for (index = 0; index < cmd_size; index++) {
		handler = &vf_mag_cmd_handler[index];
		if (cmd == handler->cmd)
			return handler->handler(nic_io, vf_id, buf_in, in_size,
						buf_out, out_size);
	}

	nic_warn(nic_io->dev_hdl, "NO handler for mag cmd: %u received from vf id: %u\n",
		 cmd, vf_id);

	return -EINVAL;
}

static struct sssnic_event_handler mag_cmd_handler[] = {
	{
		.cmd = MAG_CMD_GET_LINK_STATUS,
		.handler = link_status_event_handler,
	},

	{
		.cmd = MAG_CMD_WIRE_EVENT,
		.handler = cable_plug_event,
	},

	{
		.cmd = MAG_CMD_GET_XSFP_INFO,
		.handler = port_sfp_info_event,
	},

	{
		.cmd = MAG_CMD_GET_XSFP_PRESENT,
		.handler = port_sfp_abs_event,
	},
};

static int sssnic_mag_event_handler(void *hwdev, u16 cmd,
				    void *buf_in, u16 in_size, void *buf_out,
				    u16 *out_size)
{
	struct sssnic_nic_io *nic_io = NULL;
	u32 size = SSSHW_ARRAY_LEN(mag_cmd_handler);
	u32 i;

	if (!hwdev)
		return -EINVAL;

	*out_size = 0;
	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	for (i = 0; i < size; i++) {
		if (cmd == mag_cmd_handler[i].cmd) {
			mag_cmd_handler[i].handler(hwdev, buf_in, in_size,
						   buf_out, out_size);
			return 0;
		}
	}

	/* can't find this event cmd */
	sdk_warn(nic_io->dev_hdl, "Unsupported mag event, cmd: %u\n", cmd);
	*out_size = sizeof(struct ssshw_mgmt_msg_head);
	((struct ssshw_mgmt_msg_head *)buf_out)->status = SSSHW_MGMT_CMD_UNSUPPORTED;

	return 0;
}

int sssnic_vf_mag_event_handler(void *hwdev, u16 cmd,
				void *buf_in, u16 in_size, void *buf_out,
				u16 *out_size)
{
	return sssnic_mag_event_handler(hwdev, cmd, buf_in, in_size,
					buf_out, out_size);
}

/* pf/ppf handler mgmt cpu report ssslink event*/
void sssnic_pf_mag_event_handler(void *pri_handle, u16 cmd,
				 void *buf_in, u16 in_size, void *buf_out,
				 u16 *out_size)
{
	sssnic_mag_event_handler(pri_handle, cmd, buf_in, in_size,
				 buf_out, out_size);
}

static int _mag_msg_to_mgmt_sync(void *hwdev, u16 cmd, void *buf_in,
				 u16 in_size, void *buf_out, u16 *out_size,
				 u16 channel)
{
	u32 i, cmd_cnt = SSSHW_ARRAY_LEN(vf_mag_cmd_handler);
	bool cmd_to_pf = false;

	if (ssshw_func_type(hwdev) == SSSHW_TYPE_VF) {
		for (i = 0; i < cmd_cnt; i++) {
			if (cmd == vf_mag_cmd_handler[i].cmd) {
				cmd_to_pf = true;
				break;
			}
		}
	}

	if (cmd_to_pf)
		return ssshw_mbox_to_pf(hwdev, SSSHW_MOD_SSSLINK, cmd, buf_in,
					 in_size, buf_out, out_size, 0,
					 channel);

	return ssshw_msg_to_mgmt_sync(hwdev, SSSHW_MOD_SSSLINK, cmd, buf_in,
				       in_size, buf_out, out_size, 0, channel);
}

static int mag_msg_to_mgmt_sync(void *hwdev, u16 cmd, void *buf_in, u16 in_size,
				void *buf_out, u16 *out_size)
{
	return _mag_msg_to_mgmt_sync(hwdev, cmd, buf_in, in_size, buf_out,
				     out_size, SSSHW_CHANNEL_NIC);
}

static int mag_msg_to_mgmt_sync_ch(void *hwdev, u16 cmd, void *buf_in,
				   u16 in_size, void *buf_out, u16 *out_size,
				   u16 channel)
{
	return _mag_msg_to_mgmt_sync(hwdev, cmd, buf_in, in_size, buf_out,
				     out_size, channel);
}

//sssnic_rss_cfg.c
static int sssnic_rss_cfg_hash_key(struct sssnic_nic_io *nic_io, u8 opcode,
				   u8 *key)
{
	struct sssnic_cmd_rss_hash_key hash_key;
	u16 out_size = sizeof(hash_key);
	int err;

	memset(&hash_key, 0, sizeof(struct sssnic_cmd_rss_hash_key));
	hash_key.func_id = ssshw_global_func_id(nic_io->hwdev);
	hash_key.opcode = opcode;

	if (opcode == SSSNIC_CMD_OP_SET)
		memcpy(hash_key.key, key, SSSNIC_RSS_KEY_SIZE);

	err = l2nic_msg_to_mgmt_sync(nic_io->hwdev,
				     SSSNIC_CMD_CFG_RSS_HASH_KEY,
				     &hash_key, sizeof(hash_key),
				     &hash_key, &out_size);
	if (err || !out_size || hash_key.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to %s hash key, err: %d, status: 0x%x, out size: 0x%x\n",
			opcode == SSSNIC_CMD_OP_SET ? "set" : "get",
			err, hash_key.msg_head.status, out_size);
		return -EINVAL;
	}

	if (opcode == SSSNIC_CMD_OP_GET)
		memcpy(key, hash_key.key, SSSNIC_RSS_KEY_SIZE);

	return 0;
}

int sssnic_rss_set_hash_key(void *hwdev, const u8 *key)
{
	struct sssnic_nic_io *nic_io = NULL;
	u8 hash_key[SSSNIC_RSS_KEY_SIZE];

	if (!hwdev || !key)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memcpy(hash_key, key, SSSNIC_RSS_KEY_SIZE);
	return sssnic_rss_cfg_hash_key(nic_io, SSSNIC_CMD_OP_SET, hash_key);
}

int sssnic_rss_get_hash_key(void *hwdev, u8 *key)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev || !key)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	return sssnic_rss_cfg_hash_key(nic_io, SSSNIC_CMD_OP_GET, key);
}

int sssnic_rss_get_indir_tbl(void *hwdev, u32 *indir_table)
{
	struct ssshw_cmd_buf *cmd_buf = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	u16 *indir_tbl = NULL;
	int err, i;

	if (!hwdev || !indir_table)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	cmd_buf = sssnic_alloc_cmd_buf(hwdev);
	if (!cmd_buf) {
		nic_err(nic_io->dev_hdl, "Failed to allocate cmd_buf.\n");
		return -ENOMEM;
	}

	cmd_buf->size = sizeof(struct sssnic_rss_indirect_tbl);
	err = ssshw_cmdq_detail_resp(hwdev, SSSHW_MOD_L2NIC,
				      SSSNIC_UCODE_CMD_GET_RSS_INDIR_TABLE,
				      cmd_buf, cmd_buf, NULL, 0,
				      SSSHW_CHANNEL_NIC);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to get rss indir table\n");
		goto get_indir_tbl_failed;
	}

	indir_tbl = (u16 *)cmd_buf->buf;
	for (i = 0; i < SSSNIC_RSS_INDIR_SIZE; i++)
		indir_table[i] = *(indir_tbl + i);

get_indir_tbl_failed:
	ssshw_free_cmd_buf(hwdev, cmd_buf);

	return err;
}

int sssnic_rss_set_indir_tbl(void *hwdev, const u32 *indir_table)
{
	struct sssnic_rss_indirect_tbl *indir_tbl = NULL;
	struct ssshw_cmd_buf *cmd_buf = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	u32 *temp = NULL;
	u32 i, size;
	u64 out_param = 0;
	int err;

	if (!hwdev || !indir_table)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	cmd_buf = sssnic_alloc_cmd_buf(hwdev);
	if (!cmd_buf) {
		nic_err(nic_io->dev_hdl, "Failed to allocate cmd buf\n");
		return -ENOMEM;
	}

	cmd_buf->size = sizeof(struct sssnic_rss_indirect_tbl);
	indir_tbl = (struct sssnic_rss_indirect_tbl *)cmd_buf->buf;
	memset(indir_tbl, 0, sizeof(*indir_tbl));

	for (i = 0; i < SSSNIC_RSS_INDIR_SIZE; i++)
		indir_tbl->entry[i] = (u16)(*(indir_table + i));

	size = sizeof(indir_tbl->entry) / sizeof(u32);
	temp = (u32 *)indir_tbl->entry;
	for (i = 0; i < size; i++)
		temp[i] = cpu_to_be32(temp[i]);

	err = ssshw_cmdq_direct_resp(hwdev, SSSHW_MOD_L2NIC,
				      SSSNIC_UCODE_CMD_SET_RSS_INDIR_TABLE,
				      cmd_buf, &out_param, 0,
				      SSSHW_CHANNEL_NIC);
	if (err || out_param != 0) {
		nic_err(nic_io->dev_hdl, "Failed to set rss indir table\n");
		err = -EFAULT;
	}

	ssshw_free_cmd_buf(hwdev, cmd_buf);
	return err;
}

int sssnic_set_rss_type(void *hwdev, struct sssnic_rss_type rss_type)
{
	struct sssnic_rss_context_tbl *ctx_tbl = NULL;
	struct ssshw_cmd_buf *cmd_buf = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	u32 ctx = 0;
	u64 out_param = 0;
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	cmd_buf = sssnic_alloc_cmd_buf(hwdev);
	if (!cmd_buf) {
		nic_err(nic_io->dev_hdl, "Failed to allocate cmd buf\n");
		return -ENOMEM;
	}

	ctx |= SSSNIC_RSS_TYPE_SET(1, VALID) |
	       SSSNIC_RSS_TYPE_SET(rss_type.ipv4, IPV4) |
	       SSSNIC_RSS_TYPE_SET(rss_type.ipv6, IPV6) |
	       SSSNIC_RSS_TYPE_SET(rss_type.ipv6_ext, IPV6_EXT) |
	       SSSNIC_RSS_TYPE_SET(rss_type.tcp_ipv4, TCP_IPV4) |
	       SSSNIC_RSS_TYPE_SET(rss_type.tcp_ipv6, TCP_IPV6) |
	       SSSNIC_RSS_TYPE_SET(rss_type.tcp_ipv6_ext, TCP_IPV6_EXT) |
	       SSSNIC_RSS_TYPE_SET(rss_type.udp_ipv4, UDP_IPV4) |
	       SSSNIC_RSS_TYPE_SET(rss_type.udp_ipv6, UDP_IPV6);

	cmd_buf->size = sizeof(struct sssnic_rss_context_tbl);
	ctx_tbl = (struct sssnic_rss_context_tbl *)cmd_buf->buf;
	memset(ctx_tbl, 0, sizeof(*ctx_tbl));
	ctx_tbl->ctx = cpu_to_be32(ctx);

	/* cfg the rss context table by command queue */
	err = ssshw_cmdq_direct_resp(hwdev, SSSHW_MOD_L2NIC,
				      SSSNIC_UCODE_CMD_SET_RSS_CONTEXT_TABLE,
				      cmd_buf, &out_param, 0,
				      SSSHW_CHANNEL_NIC);

	ssshw_free_cmd_buf(hwdev, cmd_buf);

	if (err || out_param != 0) {
		nic_err(nic_io->dev_hdl, "Failed to set rss context table, err: %d\n",
			err);
		return -EFAULT;
	}

	return 0;
}

int sssnic_get_rss_type(void *hwdev, struct sssnic_rss_type *rss_type)
{
	struct sssnic_rss_context_table ctx_tbl;
	u16 out_size = sizeof(ctx_tbl);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !rss_type)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&ctx_tbl, 0, sizeof(struct sssnic_rss_context_table));
	ctx_tbl.func_id = ssshw_global_func_id(hwdev);

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_GET_RSS_CTX_TBL,
				     &ctx_tbl, sizeof(ctx_tbl),
				     &ctx_tbl, &out_size);
	if (err || !out_size || ctx_tbl.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to get hash type, err: %d, status: 0x%x, out size: 0x%x\n",
			err, ctx_tbl.msg_head.status, out_size);
			return -EINVAL;
	}

	rss_type->ipv4	       = SSSNIC_RSS_TYPE_GET(ctx_tbl.context, IPV4);
	rss_type->ipv6	       = SSSNIC_RSS_TYPE_GET(ctx_tbl.context, IPV6);
	rss_type->ipv6_ext     = SSSNIC_RSS_TYPE_GET(ctx_tbl.context, IPV6_EXT);
	rss_type->tcp_ipv4     = SSSNIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV4);
	rss_type->tcp_ipv6     = SSSNIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV6);
	rss_type->tcp_ipv6_ext = SSSNIC_RSS_TYPE_GET(ctx_tbl.context,
						     TCP_IPV6_EXT);
	rss_type->udp_ipv4     = SSSNIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV4);
	rss_type->udp_ipv6     = SSSNIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV6);

	return 0;
}

static int sssnic_rss_cfg_hash_engine(struct sssnic_nic_io *nic_io, u8 opcode,
				      u8 *type)
{
	struct sssnic_cmd_rss_engine_type hash_type;
	u16 out_size = sizeof(hash_type);
	int err;

	memset(&hash_type, 0, sizeof(struct sssnic_cmd_rss_engine_type));

	hash_type.func_id = ssshw_global_func_id(nic_io->hwdev);
	hash_type.opcode = opcode;

	if (opcode == SSSNIC_CMD_OP_SET)
		hash_type.hash_engine = *type;

	err = l2nic_msg_to_mgmt_sync(nic_io->hwdev,
				     SSSNIC_CMD_CFG_RSS_HASH_ENGINE,
				     &hash_type, sizeof(hash_type),
				     &hash_type, &out_size);
	if (err || !out_size || hash_type.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to %s hash engine, err: %d, status: 0x%x, out size: 0x%x\n",
			opcode == SSSNIC_CMD_OP_SET ? "set" : "get",
			err, hash_type.msg_head.status, out_size);
		return -EIO;
	}

	if (opcode == SSSNIC_CMD_OP_GET)
		*type = hash_type.hash_engine;

	return 0;
}

int sssnic_rss_set_hash_engine(void *hwdev, u8 type)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	return sssnic_rss_cfg_hash_engine(nic_io, SSSNIC_CMD_OP_SET, &type);
}

int sssnic_rss_get_hash_engine(void *hwdev, u8 *type)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev || !type)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	return sssnic_rss_cfg_hash_engine(nic_io, SSSNIC_CMD_OP_GET, type);
}

int sssnic_rss_cfg(void *hwdev, u8 rss_en, u8 cos_num, u8 *prio_tc, u16 num_qps)
{
	struct sssnic_cmd_rss_config rss_cfg;
	u16 out_size = sizeof(rss_cfg);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	/* micro code required: number of TC should be power of 2 */
	if (!hwdev || !prio_tc || (cos_num & (cos_num - 1)))
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memset(&rss_cfg, 0, sizeof(struct sssnic_cmd_rss_config));
	rss_cfg.func_id = ssshw_global_func_id(hwdev);
	rss_cfg.rss_en = rss_en;
	rss_cfg.rq_priority_number = cos_num ? (u8)ilog2(cos_num) : 0;
	rss_cfg.num_qps = num_qps;

	memcpy(rss_cfg.prio_tc, prio_tc, SSSNIC_DCB_UP_MAX);

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_RSS_CFG,
				     &rss_cfg, sizeof(rss_cfg),
				     &rss_cfg, &out_size);
	if (err || !out_size || rss_cfg.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set rss cfg, err: %d, status: 0x%x, out size: 0x%x\n",
			err, rss_cfg.msg_head.status, out_size);
		return -EINVAL;
	}

	return 0;
}


static int mag_msg_to_mgmt_sync(void *hwdev, u16 cmd, void *buf_in, u16 in_size,
				void *buf_out, u16 *out_size);
static int mag_msg_to_mgmt_sync_ch(void *hwdev, u16 cmd, void *buf_in,
				   u16 in_size, void *buf_out, u16 *out_size,
				   u16 channel);

int sssnic_set_port_enable(void *hwdev, bool enable, u16 channel)
{
	struct mag_cmd_set_port_enable en_state;
	u16 out_size = sizeof(en_state);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	if (ssshw_func_type(hwdev) == SSSHW_TYPE_VF)
		return 0;

	memset(&en_state, 0, sizeof(en_state));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	en_state.function_id = ssshw_global_func_id(hwdev);
	en_state.state = enable ? MAG_CMD_TX_ENABLE | MAG_CMD_RX_ENABLE :
				MAG_CMD_PORT_DISABLE;

	err = mag_msg_to_mgmt_sync_ch(hwdev, MAG_CMD_SET_PORT_ENABLE, &en_state,
				      sizeof(en_state), &en_state, &out_size,
				      channel);
	if (err || !out_size || en_state.head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set port state, err: %d, status: 0x%x, out size: 0x%x, channel: 0x%x\n",
			err, en_state.head.status, out_size, channel);
		return -EIO;
	}

	return 0;
}
EXPORT_SYMBOL(sssnic_set_port_enable);

int sssnic_get_phy_port_stats(void *hwdev, struct mag_cmd_port_stats *stats)
{
	struct mag_cmd_get_port_stat *port_stats = NULL;
	struct mag_cmd_port_stats_info stats_info;
	u16 out_size = sizeof(*port_stats);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	port_stats = kzalloc(sizeof(*port_stats), GFP_KERNEL);
	if (!port_stats)
		return -ENOMEM;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	memset(&stats_info, 0, sizeof(stats_info));
	stats_info.port_id = ssshw_physical_port_id(hwdev);

	err = mag_msg_to_mgmt_sync(hwdev, MAG_CMD_GET_PORT_STAT,
				   &stats_info, sizeof(stats_info),
				   port_stats, &out_size);
	if (err || !out_size || port_stats->head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to get port statistics, err: %d, status: 0x%x, out size: 0x%x\n",
			err, port_stats->head.status, out_size);
		err = -EIO;
		goto out;
	}

	memcpy(stats, &port_stats->counter, sizeof(*stats));

out:
	kfree(port_stats);

	return err;
}
EXPORT_SYMBOL(sssnic_get_phy_port_stats);

int sssnic_set_port_funcs_state(void *hwdev, bool enable)
{
	return 0;
}

int sssnic_reset_port_link_cfg(void *hwdev)
{
	return 0;
}

int sssnic_force_port_relink(void *hwdev)
{
	return 0;
}

int sssnic_set_autoneg(void *hwdev, bool enable)
{
	/* TODO */

	return 0;
}


//sssnic_nic_cfg.c


int sssnic_set_ci_table(void *hwdev, struct sssnic_sq_attr *attr)
{
	struct sssnic_cmd_cons_idx_attr cons_idx_attr;
	u16 out_size = sizeof(cons_idx_attr);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !attr)
		return -EINVAL;

	memset(&cons_idx_attr, 0, sizeof(cons_idx_attr));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	cons_idx_attr.func_idx = ssshw_global_func_id(hwdev);

	cons_idx_attr.dma_attr_off  = attr->dma_attr_off;
	cons_idx_attr.pending_limit = attr->pending_limit;
	cons_idx_attr.coalescing_time  = attr->coalescing_time;

	if (attr->intr_en) {
		cons_idx_attr.intr_en = attr->intr_en;
		cons_idx_attr.intr_idx = attr->intr_idx;
	}

	cons_idx_attr.l2nic_sqn = attr->l2nic_sqn;
	cons_idx_attr.ci_addr = attr->ci_dma_base;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_SQ_CI_ATTR_SET,
				     &cons_idx_attr, sizeof(cons_idx_attr),
				     &cons_idx_attr, &out_size);
	if (err || !out_size || cons_idx_attr.msg_head.status) {
		sdk_err(nic_io->dev_hdl,
			"Failed to set ci attribute table, err: %d, status: 0x%x, out_size: 0x%x\n",
			err, cons_idx_attr.msg_head.status, out_size);
		return -EFAULT;
	}

	return 0;
}

static int sssnic_check_mac_info(u8 status, u16 vlan_id)
{
	if ((status && status != SSSNIC_MGMT_STATUS_EXIST &&
	     status != SSSNIC_PF_SET_VF_ALREADY) ||
	    (vlan_id & CHECK_IPSU_15BIT &&
	     status == SSSNIC_MGMT_STATUS_EXIST))
		return -EINVAL;

	return 0;
}

#define SSSNIC_VLAN_ID_MASK	0x7FFF

int sssnic_set_mac(void *hwdev, const u8 *mac_addr, u16 vlan_id, u16 func_id,
		   u16 channel)
{
	struct sssnic_port_mac_set mac_info;
	u16 out_size = sizeof(mac_info);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !mac_addr)
		return -EINVAL;

	memset(&mac_info, 0, sizeof(mac_info));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	if ((vlan_id & SSSNIC_VLAN_ID_MASK) >= VLAN_N_VID) {
		nic_err(nic_io->dev_hdl, "Invalid VLAN number: %d\n",
			(vlan_id & SSSNIC_VLAN_ID_MASK));
		return -EINVAL;
	}

	mac_info.func_id = func_id;
	mac_info.vlan_id = vlan_id;
	ether_addr_copy(mac_info.mac, mac_addr);

	err = l2nic_msg_to_mgmt_sync_ch(hwdev, SSSNIC_CMD_SET_MAC,
					&mac_info, sizeof(mac_info),
					&mac_info, &out_size, channel);
	if (err || !out_size ||
	    sssnic_check_mac_info(mac_info.msg_head.status,
				  mac_info.vlan_id)) {
		nic_err(nic_io->dev_hdl,
			"Failed to update MAC, err: %d, status: 0x%x, out size: 0x%x, channel: 0x%x\n",
			err, mac_info.msg_head.status, out_size, channel);
		return -EINVAL;
	}

	if (mac_info.msg_head.status == SSSNIC_PF_SET_VF_ALREADY) {
		nic_warn(nic_io->dev_hdl, "PF has already set VF mac, Ignore set operation\n");
		return SSSNIC_PF_SET_VF_ALREADY;
	}

	if (mac_info.msg_head.status == SSSNIC_MGMT_STATUS_EXIST) {
		nic_warn(nic_io->dev_hdl, "MAC is repeated. Ignore update operation\n");
		return 0;
	}

	return 0;
}
EXPORT_SYMBOL(sssnic_set_mac);

int sssnic_del_mac(void *hwdev, const u8 *mac_addr, u16 vlan_id, u16 func_id,
		   u16 channel)
{
	struct sssnic_port_mac_set mac_info;
	u16 out_size = sizeof(mac_info);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !mac_addr)
		return -EINVAL;

	memset(&mac_info, 0, sizeof(mac_info));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	if ((vlan_id & SSSNIC_VLAN_ID_MASK) >= VLAN_N_VID) {
		nic_err(nic_io->dev_hdl, "Invalid VLAN number: %d\n",
			(vlan_id & SSSNIC_VLAN_ID_MASK));
		return -EINVAL;
	}

	mac_info.func_id = func_id;
	mac_info.vlan_id = vlan_id;
	ether_addr_copy(mac_info.mac, mac_addr);

	err = l2nic_msg_to_mgmt_sync_ch(hwdev, SSSNIC_CMD_DEL_MAC,
					&mac_info, sizeof(mac_info), &mac_info,
					&out_size, channel);
	if (err || !out_size ||
	    (mac_info.msg_head.status && mac_info.msg_head.status !=
	     SSSNIC_PF_SET_VF_ALREADY)) {
		nic_err(nic_io->dev_hdl,
			"Failed to delete MAC, err: %d, status: 0x%x, out size: 0x%x, channel: 0x%x\n",
			err, mac_info.msg_head.status, out_size, channel);
		return -EINVAL;
	}

	if (mac_info.msg_head.status == SSSNIC_PF_SET_VF_ALREADY) {
		nic_warn(nic_io->dev_hdl, "PF has already set VF mac, Ignore delete operation.\n");
		return SSSNIC_PF_SET_VF_ALREADY;
	}

	return 0;
}
EXPORT_SYMBOL(sssnic_del_mac);

int sssnic_update_mac(void *hwdev, u8 *old_mac, u8 *new_mac, u16 vlan_id,
		      u16 func_id)
{
	struct sssnic_port_mac_update mac_info;
	u16 out_size = sizeof(mac_info);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !old_mac || !new_mac)
		return -EINVAL;

	memset(&mac_info, 0, sizeof(mac_info));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	if ((vlan_id & SSSNIC_VLAN_ID_MASK) >= VLAN_N_VID) {
		nic_err(nic_io->dev_hdl, "Invalid VLAN number: %d\n",
			(vlan_id & SSSNIC_VLAN_ID_MASK));
		return -EINVAL;
	}

	mac_info.func_id = func_id;
	mac_info.vlan_id = vlan_id;
	ether_addr_copy(mac_info.old_mac, old_mac);
	ether_addr_copy(mac_info.new_mac, new_mac);

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_UPDATE_MAC,
				     &mac_info, sizeof(mac_info),
				     &mac_info, &out_size);
	if (err || !out_size ||
	    sssnic_check_mac_info(mac_info.msg_head.status,
				  mac_info.vlan_id)) {
		nic_err(nic_io->dev_hdl,
			"Failed to update MAC, err: %d, status: 0x%x, out size: 0x%x\n",
			err, mac_info.msg_head.status, out_size);
		return -EINVAL;
	}

	if (mac_info.msg_head.status == SSSNIC_PF_SET_VF_ALREADY) {
		nic_warn(nic_io->dev_hdl, "PF has already set VF MAC. Ignore update operation\n");
		return SSSNIC_PF_SET_VF_ALREADY;
	}

	if (mac_info.msg_head.status == SSSNIC_MGMT_STATUS_EXIST) {
		nic_warn(nic_io->dev_hdl, "MAC is repeated. Ignore update operation\n");
		return 0;
	}

	return 0;
}

int sssnic_get_default_mac(void *hwdev, u8 *mac_addr)
{
	struct sssnic_port_mac_set mac_info;
	u16 out_size = sizeof(mac_info);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !mac_addr)
		return -EINVAL;

	memset(&mac_info, 0, sizeof(mac_info));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	mac_info.func_id = ssshw_global_func_id(hwdev);

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_GET_MAC,
				     &mac_info, sizeof(mac_info),
		&mac_info, &out_size);
	if (err || !out_size || mac_info.msg_head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to get mac, err: %d, status: 0x%x, out size: 0x%x\n",
			err, mac_info.msg_head.status, out_size);
		return -EINVAL;
	}

	ether_addr_copy(mac_addr, mac_info.mac);

	return 0;
}

static int sssnic_config_vlan(struct sssnic_nic_io *nic_io, u8 opcode,
			      u16 vlan_id, u16 func_id)
{
	struct sssnic_cmd_vlan_config vlan_info;
	u16 out_size = sizeof(vlan_info);
	int err;

	memset(&vlan_info, 0, sizeof(vlan_info));
	vlan_info.opcode = opcode;
	vlan_info.func_id = func_id;
	vlan_info.vlan_id = vlan_id;

	err = l2nic_msg_to_mgmt_sync(nic_io->hwdev,
				     SSSNIC_CMD_CFG_FUNC_VLAN,
				     &vlan_info, sizeof(vlan_info),
				     &vlan_info, &out_size);
	if (err || !out_size || vlan_info.msg_head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to %s vlan, err: %d, status: 0x%x, out size: 0x%x\n",
			opcode == SSSNIC_CMD_OP_ADD ? "add" : "delete",
			err, vlan_info.msg_head.status, out_size);
		return -EINVAL;
	}

	return 0;
}

int sssnic_add_vlan(void *hwdev, u16 vlan_id, u16 func_id)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	return sssnic_config_vlan(nic_io, SSSNIC_CMD_OP_ADD, vlan_id, func_id);
}

int sssnic_del_vlan(void *hwdev, u16 vlan_id, u16 func_id)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	return sssnic_config_vlan(nic_io, SSSNIC_CMD_OP_DEL, vlan_id, func_id);
}

int sssnic_set_vport_enable(void *hwdev, u16 func_id, bool enable, u16 channel)
{
	struct sssnic_vport_state en_state;
	u16 out_size = sizeof(en_state);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	memset(&en_state, 0, sizeof(en_state));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	en_state.func_id = func_id;
	en_state.state = enable ? 1 : 0;

	err = l2nic_msg_to_mgmt_sync_ch(hwdev, SSSNIC_CMD_SET_VPORT_ENABLE,
					&en_state, sizeof(en_state),
					&en_state, &out_size, channel);
	if (err || !out_size || en_state.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set vport state, err: %d, status: 0x%x, out size: 0x%x, channel: 0x%x\n",
			err, en_state.msg_head.status, out_size, channel);
		return -EINVAL;
	}

	return 0;
}
EXPORT_SYMBOL_GPL(sssnic_set_vport_enable);

int sssnic_set_dcb_state(void *hwdev, struct sssnic_dcb_state *dcb_state)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev || !dcb_state)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!memcmp(&nic_io->dcb_state, dcb_state, sizeof(nic_io->dcb_state)))
		return 0;

	/* save in sdk, vf will get dcb state when probing */
	sssnic_save_dcb_state(nic_io, dcb_state);

	/* notify stateful in pf, than notify all vf */
	sssnic_notify_dcb_state_event(nic_io, dcb_state);

	return 0;
}

int sssnic_get_dcb_state(void *hwdev, struct sssnic_dcb_state *dcb_state)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev || !dcb_state)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	memcpy(dcb_state, &nic_io->dcb_state, sizeof(*dcb_state));

	return 0;
}
EXPORT_SYMBOL(sssnic_get_dcb_state);

int sssnic_get_cos_by_pri(void *hwdev, u8 pri, u8 *cos)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev || !cos)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	if (pri >= SSSNIC_DCB_UP_MAX && nic_io->dcb_state.trust == SSSNIC_DCB_PCP)
		return -EINVAL;

	if (pri >= SSSNIC_DCB_IP_PRI_MAX && nic_io->dcb_state.trust == SSSNIC_DCB_DSCP)
		return -EINVAL;

/*lint -e662*/
/*lint -e661*/
	if (nic_io->dcb_state.dcb_on) {
		if (nic_io->dcb_state.trust == SSSNIC_DCB_PCP)
			*cos = nic_io->dcb_state.pcp2cos[pri];
		else
			*cos = nic_io->dcb_state.dscp2cos[pri];
	} else {
		*cos = nic_io->dcb_state.default_cos;
	}
/*lint +e662*/
/*lint +e661*/
	return 0;
}
EXPORT_SYMBOL(sssnic_get_cos_by_pri);

int sssnic_save_dcb_state(struct sssnic_nic_io *nic_io,
			  struct sssnic_dcb_state *dcb_state)
{
	memcpy(&nic_io->dcb_state, dcb_state, sizeof(*dcb_state));

	return 0;
}

int sssnic_get_pf_dcb_state(void *hwdev, struct sssnic_dcb_state *dcb_state)
{
	struct sssnic_cmd_vf_dcb_state vf_dcb;
	struct sssnic_nic_io *nic_io = NULL;
	u16 out_size = sizeof(vf_dcb);
	int err;

	if (!hwdev || !dcb_state)
		return -EINVAL;

	memset(&vf_dcb, 0, sizeof(vf_dcb));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	if (ssshw_func_type(hwdev) != SSSHW_TYPE_VF) {
		nic_err(nic_io->dev_hdl, "Only vf need to get pf dcb state\n");
		return -EINVAL;
	}

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_VF_COS, &vf_dcb,
				     sizeof(vf_dcb), &vf_dcb, &out_size);
	if (err || !out_size || vf_dcb.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to get vf default cos, err: %d, status: 0x%x, out size: 0x%x\n",
			err, vf_dcb.msg_head.status, out_size);
		return -EFAULT;
	}

	memcpy(dcb_state, &vf_dcb.state, sizeof(*dcb_state));
	/* Save dcb_state in hw for stateful module */
	sssnic_save_dcb_state(nic_io, dcb_state);

	return 0;
}
EXPORT_SYMBOL(sssnic_get_pf_dcb_state);

#define UNSUPPORT_SET_PAUSE 0x10
static int sssnic_cfg_hw_pause(struct sssnic_nic_io *nic_io, u8 opcode,
			       struct sssnic_pause_config *nic_pause)
{
	struct sssnic_cmd_pause_config pause_info;
	u16 out_size = sizeof(pause_info);
	int err;

	memset(&pause_info, 0, sizeof(pause_info));

	pause_info.port_id = ssshw_physical_port_id(nic_io->hwdev);
	pause_info.opcode = opcode;
	if (opcode == SSSNIC_CMD_OP_SET) {
		pause_info.auto_neg = nic_pause->auto_neg;
		pause_info.rx_pause = nic_pause->rx_pause;
		pause_info.tx_pause = nic_pause->tx_pause;
	}

	err = l2nic_msg_to_mgmt_sync(nic_io->hwdev,
				     SSSNIC_CMD_CFG_PAUSE_INFO,
				     &pause_info, sizeof(pause_info),
				     &pause_info, &out_size);
	if (err || !out_size || pause_info.msg_head.status) {
		if (pause_info.msg_head.status == UNSUPPORT_SET_PAUSE) {
			err = -EOPNOTSUPP;
			nic_err(nic_io->dev_hdl, "Can not set pause when pfc is enable\n");
		} else {
			err = -EFAULT;
			nic_err(nic_io->dev_hdl, "Failed to %s pause info, err: %d, status: 0x%x, out size: 0x%x\n",
				opcode == SSSNIC_CMD_OP_SET ? "set" : "get",
				err, pause_info.msg_head.status, out_size);
		}
		return err;
	}

	if (opcode == SSSNIC_CMD_OP_GET) {
		nic_pause->auto_neg = pause_info.auto_neg;
		nic_pause->rx_pause = pause_info.rx_pause;
		nic_pause->tx_pause = pause_info.tx_pause;
	}

	return 0;
}

int sssnic_set_pause_info(void *hwdev, struct sssnic_pause_config nic_pause)
{
	struct sssnic_nic_cfg *nic_cfg = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	nic_cfg = &nic_io->nic_cfg;

	down(&nic_cfg->cfg_lock);

	err = sssnic_cfg_hw_pause(nic_io, SSSNIC_CMD_OP_SET, &nic_pause);
	if (err) {
		up(&nic_cfg->cfg_lock);
		return err;
	}

	nic_cfg->pfc_en = 0;
	nic_cfg->pfc_bitmap = 0;
	nic_cfg->pause_set = true;
	nic_cfg->nic_pause.auto_neg = nic_pause.auto_neg;
	nic_cfg->nic_pause.rx_pause = nic_pause.rx_pause;
	nic_cfg->nic_pause.tx_pause = nic_pause.tx_pause;

	up(&nic_cfg->cfg_lock);

	return 0;
}

int sssnic_get_pause_info(void *hwdev, struct sssnic_pause_config *nic_pause)
{
	struct sssnic_nic_cfg *nic_cfg = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	int err = 0;

	if (!hwdev || !nic_pause)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	nic_cfg = &nic_io->nic_cfg;

	err = sssnic_cfg_hw_pause(nic_io, SSSNIC_CMD_OP_GET, nic_pause);
	if (err)
		return err;

	if (nic_cfg->pause_set || !nic_pause->auto_neg) {
		nic_pause->rx_pause = nic_cfg->nic_pause.rx_pause;
		nic_pause->tx_pause = nic_cfg->nic_pause.tx_pause;
	}

	return 0;
}

int sssnic_sync_dcb_state(void *hwdev, u8 op_code, u8 state)
{
	struct sssnic_cmd_set_dcb_state dcb_state;
	struct sssnic_nic_io *nic_io = NULL;
	u16 out_size = sizeof(dcb_state);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&dcb_state, 0, sizeof(dcb_state));

	dcb_state.op_code = op_code;
	dcb_state.state = state;
	dcb_state.func_id = ssshw_global_func_id(hwdev);

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_QOS_DCB_STATE,
				     &dcb_state, sizeof(dcb_state), &dcb_state, &out_size);
	if (err || dcb_state.head.status || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Failed to set dcb state, err: %d, status: 0x%x, out size: 0x%x\n",
			err, dcb_state.head.status, out_size);
		return -EFAULT;
	}

	return 0;
}

int sssnic_dcb_set_rq_iq_mapping(void *hwdev, u32 num_rqs, u8 *map,
				 u32 max_map_num)
{
	return 0;
}
EXPORT_SYMBOL(sssnic_dcb_set_rq_iq_mapping);

int sssnic_flush_qps_res(void *hwdev)
{
	struct sssnic_cmd_clear_qp_resource sq_res;
	u16 out_size = sizeof(sq_res);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	memset(&sq_res, 0, sizeof(sq_res));

	sq_res.func_id = ssshw_global_func_id(hwdev);

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_CLEAR_QP_RESOURCE,
				     &sq_res, sizeof(sq_res), &sq_res,
				     &out_size);
	if (err || !out_size || sq_res.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to clear sq resources, err: %d, status: 0x%x, out size: 0x%x\n",
			err, sq_res.msg_head.status, out_size);
		return -EINVAL;
	}

	return 0;
}
EXPORT_SYMBOL(sssnic_flush_qps_res);

int sssnic_get_fpga_phy_port_stats(void *hwdev, struct sssnic_phy_fpga_port_stats *stats)
{
	struct sssnic_port_stats *port_stats = NULL;
	struct sssnic_port_stats_info stats_info;
	u16 out_size = sizeof(*port_stats);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	port_stats = kzalloc(sizeof(*port_stats), GFP_KERNEL);
	if (!port_stats)
		return -ENOMEM;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	memset(&stats_info, 0, sizeof(stats_info));

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_GET_PORT_STAT,
				     &stats_info, sizeof(stats_info),
				     port_stats, &out_size);
	if (err || !out_size || port_stats->msg_head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to get port statistics, err: %d, status: 0x%x, out size: 0x%x\n",
			err, port_stats->msg_head.status, out_size);
		err = -EIO;
		goto out;
	}

	memcpy(stats, &port_stats->stats, sizeof(*stats));

out:
	kfree(port_stats);

	return err;
}
EXPORT_SYMBOL(sssnic_get_fpga_phy_port_stats);

int sssnic_get_vport_stats(void *hwdev, u16 func_id, struct sssnic_vport_stats *stats)
{
	struct sssnic_port_stats_info stats_info;
	struct sssnic_cmd_vport_stats vport_stats;
	u16 out_size = sizeof(vport_stats);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !stats)
		return -EINVAL;

	memset(&stats_info, 0, sizeof(stats_info));
	memset(&vport_stats, 0, sizeof(vport_stats));

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	stats_info.func_id = func_id;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_GET_VPORT_STAT,
				     &stats_info, sizeof(stats_info),
				     &vport_stats, &out_size);
	if (err || !out_size || vport_stats.msg_head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to get function statistics, err: %d, status: 0x%x, out size: 0x%x\n",
			err, vport_stats.msg_head.status, out_size);
		return -EFAULT;
	}

	memcpy(stats, &vport_stats.stats, sizeof(*stats));

	return 0;
}

static int sssnic_set_function_table(struct sssnic_nic_io *nic_io, u32 cfg_bitmap,
				     const struct sssnic_func_tbl_cfg *cfg)
{
	struct sssnic_cmd_set_func_tbl cmd_func_tbl;
	u16 out_size = sizeof(cmd_func_tbl);
	int err;

	memset(&cmd_func_tbl, 0, sizeof(cmd_func_tbl));
	cmd_func_tbl.func_id = ssshw_global_func_id(nic_io->hwdev);
	cmd_func_tbl.cfg_bitmap = cfg_bitmap;
	cmd_func_tbl.tbl_cfg = *cfg;

	err = l2nic_msg_to_mgmt_sync(nic_io->hwdev,
				     SSSNIC_CMD_SET_FUNC_TBL,
				     &cmd_func_tbl, sizeof(cmd_func_tbl),
				     &cmd_func_tbl, &out_size);
	if (err || cmd_func_tbl.msg_head.status || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Failed to set func table, bitmap: 0x%x, err: %d, status: 0x%x, out size: 0x%x\n",
			cfg_bitmap, err, cmd_func_tbl.msg_head.status,
			out_size);
		return -EFAULT;
	}

	return 0;
}

static int sssnic_init_function_table(struct sssnic_nic_io *nic_io)
{
	struct sssnic_func_tbl_cfg func_tbl_cfg = {0};
	u32 cfg_bitmap = BIT(FUNC_CFG_INIT) | BIT(FUNC_CFG_MTU) |
			BIT(FUNC_CFG_RX_BUF_SIZE);

	func_tbl_cfg.mtu = 0x3FFF; /* default, max mtu */
	func_tbl_cfg.rx_wqe_buf_size = nic_io->rx_buff_len;

	return sssnic_set_function_table(nic_io, cfg_bitmap, &func_tbl_cfg);
}

int sssnic_set_port_mtu(void *hwdev, u16 new_mtu)
{
	struct sssnic_func_tbl_cfg func_tbl_cfg = {0};
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	if (new_mtu < SSSNIC_MIN_MTU_SIZE) {
		nic_err(nic_io->dev_hdl,
			"Invalid mtu size: %ubytes, mtu size < %ubytes",
			new_mtu, SSSNIC_MIN_MTU_SIZE);
		return -EINVAL;
	}

	if (new_mtu > SSSNIC_MAX_JUMBO_FRAME_SIZE) {
		nic_err(nic_io->dev_hdl, "Invalid mtu size: %ubytes, mtu size > %ubytes",
			new_mtu, SSSNIC_MAX_JUMBO_FRAME_SIZE);
		return -EINVAL;
	}

	func_tbl_cfg.mtu = new_mtu;
	return sssnic_set_function_table(nic_io, BIT(FUNC_CFG_MTU),
					 &func_tbl_cfg);
}

static int nic_feature_nego(void *hwdev, u8 opcode, u64 *s_feature, u16 size)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_cmd_feature_nego feature_nego;
	u16 out_size = sizeof(feature_nego);
	int err;

	if (!hwdev || !s_feature || size > NIC_MAX_FEATURE_QWORD)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memset(&feature_nego, 0, sizeof(feature_nego));
	feature_nego.func_id = ssshw_global_func_id(hwdev);
	feature_nego.opcode = opcode;
	if (opcode == SSSNIC_CMD_OP_SET)
		memcpy(feature_nego.s_feature, s_feature, size * sizeof(u64));

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_FEATURE_NEGO,
				     &feature_nego, sizeof(feature_nego),
				     &feature_nego, &out_size);
	if (err || !out_size || feature_nego.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to negotiate nic feature, err:%d, status: 0x%x, out_size: 0x%x\n",
			err, feature_nego.msg_head.status, out_size);
		return -EIO;
	}

	if (opcode == SSSNIC_CMD_OP_GET)
		memcpy(s_feature, feature_nego.s_feature, size * sizeof(u64));

	return 0;
}

static int sssnic_get_bios_pf_bw_limit(void *hwdev, u32 *pf_bw_limit)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_cmd_bios_cfg cfg = {{0}};
	u16 out_size = sizeof(cfg);
	int err;

	if (!hwdev || !pf_bw_limit)
		return -EINVAL;

	if (ssshw_func_type(hwdev) == SSSHW_TYPE_VF || !SSSNIC_SUPPORT_RATE_LIMIT(hwdev))
		return 0;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	cfg.bios_cfg.func_id = (u8)ssshw_global_func_id(hwdev);
	cfg.bios_cfg.func_valid = 1;
	cfg.op_code = 0 | NIC_NVM_DATA_PF_SPEED_LIMIT;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_BIOS_CFG, &cfg, sizeof(cfg),
				     &cfg, &out_size);
	if (err || !out_size || cfg.head.status) {
		nic_err(nic_io->dev_hdl,
			"Failed to get bios pf bandwidth limit, err: %d, status: 0x%x, out size: 0x%x\n",
			err, cfg.head.status, out_size);
		return -EIO;
	}

	/* check data is valid or not */
	if (cfg.bios_cfg.signature != BIOS_CFG_SIGNATURE)
		nic_warn(nic_io->dev_hdl, "Invalid bios configuration data, signature: 0x%x\n",
			 cfg.bios_cfg.signature);

	if (cfg.bios_cfg.pf_bw > MAX_LIMIT_BW) {
		nic_err(nic_io->dev_hdl, "Invalid bios cfg pf bandwidth limit: %u\n",
			cfg.bios_cfg.pf_bw);
		return -EINVAL;
	}

	*pf_bw_limit = cfg.bios_cfg.pf_bw;

	return 0;
}

int sssnic_set_pf_rate(void *hwdev, u8 speed_level)
{
	struct sssnic_cmd_tx_rate_cfg rate_cfg = {{0}};
	struct sssnic_nic_io *nic_io = NULL;
	u16 out_size = sizeof(rate_cfg);
	u32 pf_rate;
	int err;
	u32 speed_convert[PORT_SPEED_UNKNOWN] = {
		0, 10, 100, 1000, 10000, 25000, 40000, 50000, 100000, 200000
	};

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return -EINVAL;

	if (speed_level >= PORT_SPEED_UNKNOWN) {
		nic_err(nic_io->dev_hdl, "Invalid speed level: %hhu\n", speed_level);
		return -EINVAL;
	}

	if (nic_io->nic_cfg.pf_bw_limit == MAX_LIMIT_BW) {
		pf_rate = 0;
	} else {
		/* divided by 100 to convert to percentage */
		pf_rate = (speed_convert[speed_level] / 100) * nic_io->nic_cfg.pf_bw_limit;
		/* bandwidth limit is very small but not unlimit in this case */
		if (pf_rate == 0 && speed_level != PORT_SPEED_NOT_SET)
			pf_rate = 1;
	}

	rate_cfg.func_id = ssshw_global_func_id(hwdev);
	rate_cfg.min_rate = 0;
	rate_cfg.max_rate = pf_rate;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_SET_MAX_MIN_RATE, &rate_cfg,
				     sizeof(rate_cfg), &rate_cfg, &out_size);
	if (err || !out_size || rate_cfg.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set rate(%u), err: %d, status: 0x%x, out size: 0x%x\n",
			pf_rate, err, rate_cfg.msg_head.status, out_size);
		return rate_cfg.msg_head.status ? rate_cfg.msg_head.status : -EIO;
	}

	return 0;
}

static int sssnic_get_nic_feature_from_hw(void *hwdev, u64 *s_feature, u16 size)
{
	return nic_feature_nego(hwdev, SSSNIC_CMD_OP_GET, s_feature, size);
}

int sssnic_set_nic_feature_to_hw(void *hwdev)
{
	struct sssnic_nic_io *nic_io = NULL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	return nic_feature_nego(hwdev, SSSNIC_CMD_OP_SET, &nic_io->feature_cap, 1);
}

u64 sssnic_get_feature_cap(void *hwdev)
{
	struct sssnic_nic_io *nic_io = NULL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	return nic_io->feature_cap;
}

void sssnic_update_nic_feature(void *hwdev, u64 s_feature)
{
	struct sssnic_nic_io *nic_io = NULL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	nic_io->feature_cap = s_feature;

	nic_info(nic_io->dev_hdl, "Update nic feature to 0x%llx\n", nic_io->feature_cap);
}

static inline int init_nic_hwdev_param_valid(const void *hwdev, void *pcidev_hdl,
					     const void *dev_hdl)
{
	if (!hwdev || !pcidev_hdl || !dev_hdl)
		return -EINVAL;

	return 0;
}

/*
 * sssnic_init_nic_hwdev - init nic hwdev
 * @hwdev: pointer to hwdev
 * @pcidev_hdl: pointer to pcidev or handler
 * @dev_hdl: pointer to pcidev->dev or handler, for sdk_err() or dma_alloc()
 * @rx_buff_len: rx_buff_len is receive buffer length
 */
int sssnic_init_nic_hwdev(void *hwdev, void *pcidev_hdl, void *dev_hdl,
			  u16 rx_buff_len)
{
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (init_nic_hwdev_param_valid(hwdev, pcidev_hdl, dev_hdl))
		return -EINVAL;

	nic_io = kzalloc(sizeof(*nic_io), GFP_KERNEL);
	if (!nic_io)
		return -ENOMEM;

	nic_io->dev_hdl = dev_hdl;
	nic_io->pcidev_hdl = pcidev_hdl;
	nic_io->hwdev = hwdev;

	sema_init(&nic_io->nic_cfg.cfg_lock, 1);
	mutex_init(&nic_io->nic_cfg.sfp_mutex);

	nic_io->nic_cfg.rt_cmd.mpu_send_sfp_abs = false;
	nic_io->nic_cfg.rt_cmd.mpu_send_sfp_info = false;

	err = ssshw_register_service_adapter(hwdev, nic_io, SSSHW_SERVICE_T_NIC);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to register service adapter\n");
		goto register_sa_err;
	}

	err = ssshw_set_func_svc_used_state(hwdev, SSSHW_SVC_T_NIC, 1,
					     SSSHW_CHANNEL_NIC);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to set function svc used state\n");
		goto set_used_state_err;
	}

	err = sssnic_init_function_table(nic_io);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to init function table\n");
		goto init_func_tbl_err;
	}

	err = sssnic_get_nic_feature_from_hw(hwdev, &nic_io->feature_cap, 1);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to get nic features\n");
		goto get_feature_err;
	}

	sdk_info(dev_hdl, "nic features: 0x%llx\n", nic_io->feature_cap);

	err = sssnic_get_bios_pf_bw_limit(hwdev, &nic_io->nic_cfg.pf_bw_limit);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to get pf bandwidth limit\n");
		goto get_pf_bw_err;
	}

	err = ssshw_aeq_register_swe_cb(hwdev, nic_io, SSSHW_STATELESS_EVENT,
					 sssnic_nic_sw_aeqe_handler);
	if (err) {
		nic_err(nic_io->dev_hdl,
			"Failed to register sw aeqe handler\n");
		goto register_sw_aeqe_err;
	}

	err = sssnic_vf_func_init(nic_io);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to init vf info\n");
		goto vf_init_err;
	}

	nic_io->rx_buff_len = rx_buff_len;

	return 0;

vf_init_err:
	ssshw_aeq_unregister_swe_cb(hwdev, SSSHW_STATELESS_EVENT);

register_sw_aeqe_err:
get_pf_bw_err:
get_feature_err:
init_func_tbl_err:
	ssshw_set_func_svc_used_state(hwdev, SSSHW_SVC_T_NIC, 0, SSSHW_CHANNEL_NIC);

set_used_state_err:
	ssshw_unregister_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

register_sa_err:
	mutex_deinit(&nic_io->nic_cfg.sfp_mutex);
	sema_deinit(&nic_io->nic_cfg.cfg_lock);

	kfree(nic_io);

	return err;
}
EXPORT_SYMBOL(sssnic_init_nic_hwdev);

void sssnic_free_nic_hwdev(void *hwdev)
{
	struct sssnic_nic_io *nic_io = NULL;

	if (!hwdev)
		return;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (!nic_io)
		return;

	sssnic_vf_func_free(nic_io);

	ssshw_aeq_unregister_swe_cb(hwdev, SSSHW_STATELESS_EVENT);

	ssshw_set_func_svc_used_state(hwdev, SSSHW_SVC_T_NIC, 0, SSSHW_CHANNEL_NIC);

	ssshw_unregister_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	mutex_deinit(&nic_io->nic_cfg.sfp_mutex);
	sema_deinit(&nic_io->nic_cfg.cfg_lock);

	kfree(nic_io);
}
EXPORT_SYMBOL(sssnic_free_nic_hwdev);

/* to do : send cmd to MPU to drop nic tx pkt*/
int sssnic_force_drop_tx_pkt(void *hwdev)
{
	return 0;
}

int sssnic_set_rx_mode(void *hwdev, u32 enable)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_rx_mode_config rx_mode_cfg;
	u16 out_size = sizeof(rx_mode_cfg);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&rx_mode_cfg, 0, sizeof(rx_mode_cfg));
	rx_mode_cfg.func_id = ssshw_global_func_id(hwdev);
	rx_mode_cfg.rx_mode = enable;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_SET_RX_MODE,
				     &rx_mode_cfg, sizeof(rx_mode_cfg),
				     &rx_mode_cfg, &out_size);
	if (err || !out_size || rx_mode_cfg.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set rx mode, err: %d, status: 0x%x, out size: 0x%x\n",
			err, rx_mode_cfg.msg_head.status, out_size);
		return -EINVAL;
	}

	return 0;
}

int sssnic_set_rx_vlan_offload(void *hwdev, u8 en)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_cmd_vlan_offload vlan_cfg;
	u16 out_size = sizeof(vlan_cfg);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&vlan_cfg, 0, sizeof(vlan_cfg));
	vlan_cfg.func_id = ssshw_global_func_id(hwdev);
	vlan_cfg.vlan_offload = en;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_SET_RX_VLAN_OFFLOAD,
				     &vlan_cfg, sizeof(vlan_cfg),
				     &vlan_cfg, &out_size);
	if (err || !out_size || vlan_cfg.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set rx vlan offload, err: %d, status: 0x%x, out size: 0x%x\n",
			err, vlan_cfg.msg_head.status, out_size);
		return -EINVAL;
	}

	return 0;
}

int sssnic_update_mac_vlan(void *hwdev, u16 old_vlan, u16 new_vlan, int vf_id)
{
	struct vf_data_storage *vf_info = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	u16 func_id;
	int err;

	if (!hwdev || old_vlan >= VLAN_N_VID || new_vlan >= VLAN_N_VID)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	vf_info = nic_io->vf_infos + HW_VF_ID_TO_OS(vf_id);
	if (!nic_io->vf_infos || is_zero_ether_addr(vf_info->drv_mac_addr))
		return 0;

	func_id = ssshw_glb_pf_vf_offset(nic_io->hwdev) + (u16)vf_id;

	err = sssnic_del_mac(nic_io->hwdev, vf_info->drv_mac_addr,
			     old_vlan, func_id, SSSHW_CHANNEL_NIC);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to delete VF %d MAC %pM vlan %u\n",
			HW_VF_ID_TO_OS(vf_id), vf_info->drv_mac_addr, old_vlan);
		return err;
	}

	err = sssnic_set_mac(nic_io->hwdev, vf_info->drv_mac_addr,
			     new_vlan, func_id, SSSHW_CHANNEL_NIC);
	if (err) {
		nic_err(nic_io->dev_hdl, "Failed to add VF %d MAC %pM vlan %u\n",
			HW_VF_ID_TO_OS(vf_id), vf_info->drv_mac_addr, new_vlan);
		sssnic_set_mac(nic_io->hwdev, vf_info->drv_mac_addr,
			       old_vlan, func_id, SSSHW_CHANNEL_NIC);
		return err;
	}

	return 0;
}

static int sssnic_set_rx_lro(void *hwdev, u8 ipv4_en, u8 ipv6_en,
			     u8 lro_max_pkt_len)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_cmd_lro_config lro_cfg;
	u16 out_size = sizeof(lro_cfg);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&lro_cfg, 0, sizeof(lro_cfg));
	lro_cfg.func_id = ssshw_global_func_id(hwdev);
	lro_cfg.opcode = SSSNIC_CMD_OP_SET;
	lro_cfg.lro_ipv4_en = ipv4_en;
	lro_cfg.lro_ipv6_en = ipv6_en;
	lro_cfg.lro_max_pkt_len = lro_max_pkt_len;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_CFG_RX_LRO,
				     &lro_cfg, sizeof(lro_cfg),
				     &lro_cfg, &out_size);
	if (err || !out_size || lro_cfg.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set lro offload, err: %d, status: 0x%x, out size: 0x%x\n",
			err, lro_cfg.msg_head.status, out_size);
		return -EINVAL;
	}

	return 0;
}

static int sssnic_set_rx_lro_timer(void *hwdev, u32 timer_value)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_cmd_lro_timer lro_timer;
	u16 out_size = sizeof(lro_timer);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&lro_timer, 0, sizeof(lro_timer));
	lro_timer.opcode = SSSNIC_CMD_OP_SET;
	lro_timer.timer = timer_value;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_CFG_LRO_TIMER,
				     &lro_timer, sizeof(lro_timer),
				     &lro_timer, &out_size);
	if (err || !out_size || lro_timer.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set lro timer, err: %d, status: 0x%x, out size: 0x%x\n",
			err, lro_timer.msg_head.status, out_size);

		return -EINVAL;
	}

	return 0;
}

int sssnic_set_rx_lro_state(void *hwdev, u8 lro_en, u32 lro_timer,
			    u32 lro_max_pkt_len)
{
	struct sssnic_nic_io *nic_io = NULL;
	u8 ipv4_en = 0, ipv6_en = 0;
	int err;

	if (!hwdev)
		return -EINVAL;

	ipv4_en = lro_en ? 1 : 0;
	ipv6_en = lro_en ? 1 : 0;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	nic_info(nic_io->dev_hdl, "Set LRO max coalesce packet size to %uK\n",
		 lro_max_pkt_len);

	err = sssnic_set_rx_lro(hwdev, ipv4_en, ipv6_en, (u8)lro_max_pkt_len);
	if (err)
		return err;

	/* we don't set LRO timer for VF */
	if (ssshw_func_type(hwdev) == SSSHW_TYPE_VF)
		return 0;

	nic_info(nic_io->dev_hdl, "Set LRO timer to %u\n", lro_timer);

	return sssnic_set_rx_lro_timer(hwdev, lro_timer);
}

int sssnic_set_vlan_fliter(void *hwdev, u32 vlan_filter_ctrl)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_cmd_set_vlan_filter vlan_filter;
	u16 out_size = sizeof(vlan_filter);
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);

	memset(&vlan_filter, 0, sizeof(vlan_filter));
	vlan_filter.func_id = ssshw_global_func_id(hwdev);
	vlan_filter.vlan_filter_ctrl = vlan_filter_ctrl;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_SET_VLAN_FILTER_EN,
				     &vlan_filter, sizeof(vlan_filter),
				     &vlan_filter, &out_size);
	if (err || !out_size || vlan_filter.msg_head.status) {
		nic_err(nic_io->dev_hdl, "Failed to set vlan filter, err: %d, status: 0x%x, out size: 0x%x\n",
			err, vlan_filter.msg_head.status, out_size);
		return -EINVAL;
	}

	return 0;
}

int sssnic_set_func_capture_en(void *hwdev, u16 func_id, bool cap_en)
{
	//struct ssshw_hwdev *dev = hwdev;
	struct sssnic_cmd_capture_info cap_info = {{0}};
	u16 out_size = sizeof(cap_info);
	int err;

	if (!hwdev)
		return -EINVAL;

	/* 2 function capture types */
	//cap_info.op_type = UP_UCAPTURE_OP_TYPE_FUNC;
	cap_info.is_en_trx = cap_en;
	cap_info.func_port = func_id;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_SET_UCAPTURE_OPT,
				     &cap_info, sizeof(cap_info),
				     &cap_info, &out_size);
	if (err || !out_size || cap_info.msg_head.status)
		return -EINVAL;

	return 0;
}
EXPORT_SYMBOL(sssnic_set_func_capture_en);

int sssnic_add_tcam_rule(void *hwdev, struct sssnic_tcam_cfg_rule *tcam_rule)
{
	u16 out_size = sizeof(struct sssnic_cmd_fdir_add_rule);
	struct sssnic_cmd_fdir_add_rule tcam_cmd;
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !tcam_rule)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (tcam_rule->index >= SSSNIC_MAX_TCAM_RULES_NUM) {
		nic_err(nic_io->dev_hdl, "Tcam rules num to add is invalid\n");
		return -EINVAL;
	}

	memset(&tcam_cmd, 0, sizeof(struct sssnic_cmd_fdir_add_rule));
	memcpy((void *)&tcam_cmd.rule, (void *)tcam_rule,
	       sizeof(struct sssnic_tcam_cfg_rule));
	tcam_cmd.func_id = ssshw_global_func_id(hwdev);
	tcam_cmd.type = TCAM_RULE_FDIR_TYPE;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_ADD_TC_FLOW,
				     &tcam_cmd, sizeof(tcam_cmd),
				     &tcam_cmd, &out_size);
	if (err || tcam_cmd.head.status || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Add tcam rule failed, err: %d, status: 0x%x, out size: 0x%x\n",
			err, tcam_cmd.head.status, out_size);
		return -EIO;
	}

	return 0;
}

int sssnic_del_tcam_rule(void *hwdev, u32 index)
{
	u16 out_size = sizeof(struct sssnic_cmd_fdir_del_rules);
	struct sssnic_cmd_fdir_del_rules tcam_cmd;
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (index >= SSSNIC_MAX_TCAM_RULES_NUM) {
		nic_err(nic_io->dev_hdl, "Tcam rules num to del is invalid\n");
		return -EINVAL;
	}

	memset(&tcam_cmd, 0, sizeof(struct sssnic_cmd_fdir_del_rules));
	tcam_cmd.index_start = index;
	tcam_cmd.index_num = 1;
	tcam_cmd.func_id = ssshw_global_func_id(hwdev);
	tcam_cmd.type = TCAM_RULE_FDIR_TYPE;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_DEL_TC_FLOW,
				     &tcam_cmd, sizeof(tcam_cmd),
				     &tcam_cmd, &out_size);
	if (err || tcam_cmd.head.status || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Del tcam rule failed, err: %d, status: 0x%x, out size: 0x%x\n",
			err, tcam_cmd.head.status, out_size);
		return -EIO;
	}

	return 0;
}

/**
 * sssnic_mgmt_tcam_block - alloc or free tcam block for IO packet.
 *
 * @param hwdev
 *   The hardware interface of a nic device.
 * @param alloc_en
 *   1 alloc block.
 *   0 free block.
 * @param index
 *   block index from firmware.
 * @return
 *   0 on success,
 *   negative error value otherwise.
 */
static int sssnic_mgmt_tcam_block(void *hwdev, u8 alloc_en, u16 *index)
{
	struct sssnic_cmd_ctrl_tcam_block_out tcam_block_info;
	u16 out_size = sizeof(struct sssnic_cmd_ctrl_tcam_block_out);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev || !index)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memset(&tcam_block_info, 0,
	       sizeof(struct sssnic_cmd_ctrl_tcam_block_out));

	tcam_block_info.func_id = ssshw_global_func_id(hwdev);
	tcam_block_info.alloc_en = alloc_en;
	tcam_block_info.tcam_type = NIC_TCAM_BLOCK_TYPE_LARGE;
	tcam_block_info.tcam_block_index = *index;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_CFG_TCAM_BLOCK,
				     &tcam_block_info, sizeof(tcam_block_info),
				     &tcam_block_info, &out_size);
	if (err || tcam_block_info.head.status || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Set tcam block failed, err: %d, status: 0x%x, out size: 0x%x\n",
			err, tcam_block_info.head.status, out_size);
		return -EIO;
	}

	if (alloc_en)
		*index = tcam_block_info.tcam_block_index;

	return 0;
}

int sssnic_alloc_tcam_block(void *hwdev, u16 *index)
{
	return sssnic_mgmt_tcam_block(hwdev, SSSNIC_TCAM_BLOCK_ENABLE, index);
}

int sssnic_free_tcam_block(void *hwdev, u16 *index)
{
	return sssnic_mgmt_tcam_block(hwdev, SSSNIC_TCAM_BLOCK_DISABLE, index);
}

int sssnic_set_fdir_tcam_rule_filter(void *hwdev, bool enable)
{
	struct sssnic_cmd_set_tcam_enable port_tcam_cmd;
	u16 out_size = sizeof(port_tcam_cmd);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memset(&port_tcam_cmd, 0, sizeof(port_tcam_cmd));
	port_tcam_cmd.func_id = ssshw_global_func_id(hwdev);
	port_tcam_cmd.tcam_enable = (u8)enable;

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_ENABLE_TCAM,
				     &port_tcam_cmd, sizeof(port_tcam_cmd),
				     &port_tcam_cmd, &out_size);
	if (err || port_tcam_cmd.head.status || !out_size) {
		nic_err(nic_io->dev_hdl, "Set fdir tcam filter failed, err: %d, status: 0x%x, out size: 0x%x, enable: 0x%x\n",
			err, port_tcam_cmd.head.status, out_size,
			enable);
		return -EIO;
	}

	return 0;
}

int sssnic_flush_tcam_rule(void *hwdev)
{
	struct sssnic_cmd_flush_tcam_rules tcam_flush;
	u16 out_size = sizeof(struct sssnic_cmd_flush_tcam_rules);
	struct sssnic_nic_io *nic_io = NULL;
	int err;

	if (!hwdev)
		return -EINVAL;

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	memset(&tcam_flush, 0, sizeof(struct sssnic_cmd_flush_tcam_rules));
	tcam_flush.func_id = ssshw_global_func_id(hwdev);

	err = l2nic_msg_to_mgmt_sync(hwdev, SSSNIC_CMD_FLUSH_TCAM,
				     &tcam_flush,
				     sizeof(struct sssnic_cmd_flush_tcam_rules),
				     &tcam_flush, &out_size);
	if (err || tcam_flush.head.status || !out_size) {
		nic_err(nic_io->dev_hdl,
			"Flush tcam fdir rules failed, err: %d, status: 0x%x, out size: 0x%x\n",
			err, tcam_flush.head.status, out_size);
		return -EIO;
	}

	return 0;
}


int sssnic_dbg_get_wqe_info(void *hwdev, u16 q_id, u16 idx, u16 wqebb_cnt,
			    u8 *wqe, const u16 *wqe_size,
			    enum sssnic_queue_type q_type)
{
	struct sssnic_io_queue *queue = NULL;
	struct sssnic_nic_io *nic_io = NULL;
	void *src_wqebb = NULL;
	u32 i, offset;

	if (!hwdev) {
		pr_err("hwdev is NULL.\n");
		return -EINVAL;
	}

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (q_id >= nic_io->num_qps) {
		pr_err("q_id[%u] > num_qps_cfg[%u].\n", q_id, nic_io->num_qps);
		return -EINVAL;
	}

	queue = (q_type == SSSNIC_SQ) ?
			&nic_io->sq[q_id] : &nic_io->rq[q_id];

	if ((idx + wqebb_cnt) > queue->wq.q_depth) {
		pr_err("(idx[%u] + idx[%u]) > q_depth[%u].\n",
		       idx, wqebb_cnt, queue->wq.q_depth);
		return -EINVAL;
	}

	if (*wqe_size != (queue->wq.wqebb_size * wqebb_cnt)) {
		pr_err("Unexpect out buf size from user :%u, expect: %d\n",
		       *wqe_size, (queue->wq.wqebb_size * wqebb_cnt));
		return -EINVAL;
	}

	for (i = 0; i < wqebb_cnt; i++) {
		src_wqebb = sssnic_wq_wqebb_addr(&queue->wq,
						 SSSHW_WQ_MASK_IDX(&queue->wq,
							     idx + i));
		offset = queue->wq.wqebb_size * i;
		memcpy(wqe + offset, src_wqebb, queue->wq.wqebb_size);
	}

	return 0;
}

int sssnic_dbg_get_sq_info(void *hwdev, u16 q_id, struct ssshw_nic_sq_info *sq_info,
			   u32 msg_size)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_io_queue *sq = NULL;

	if (!hwdev || !sq_info) {
		pr_err("hwdev or sq_info is NULL.\n");
		return -EINVAL;
	}

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (q_id >= nic_io->num_qps) {
		nic_err(nic_io->dev_hdl, "Input queue id(%u) is larger than the actual queue number\n",
			q_id);
		return -EINVAL;
	}

	if (msg_size != sizeof(*sq_info)) {
		nic_err(nic_io->dev_hdl, "Unexpect out buf size from user :%u, expect: %lu\n",
			msg_size, sizeof(*sq_info));
		return -EINVAL;
	}

	sq = &nic_io->sq[q_id];

	sq_info->q_id = q_id;
	sq_info->pi = sssnic_get_sq_local_pi(sq);
	sq_info->ci = sssnic_get_sq_local_ci(sq);
	sq_info->fi = sssnic_get_sq_hw_ci(sq);
	sq_info->q_depth = sq->wq.q_depth;
	sq_info->wqebb_size = sq->wq.wqebb_size;

	sq_info->ci_addr = sq->tx.cons_idx_addr;

	sq_info->cla_addr = sq->wq.wq_block_paddr;
	sq_info->slq_handle = sq;

	sq_info->doorbell.map_addr = (u64 *)sq->db_addr;

	return 0;
}

int sssnic_dbg_get_rq_info(void *hwdev, u16 q_id, struct ssshw_nic_rq_info *rq_info,
			   u32 msg_size)
{
	struct sssnic_nic_io *nic_io = NULL;
	struct sssnic_io_queue *rq = NULL;

	if (!hwdev || !rq_info) {
		pr_err("hwdev or rq_info is NULL.\n");
		return -EINVAL;
	}

	nic_io = sssnic_get_service_adapter(hwdev, SSSHW_SERVICE_T_NIC);
	if (q_id >= nic_io->num_qps) {
		nic_err(nic_io->dev_hdl, "Input queue id(%u) is larger than the actual queue number\n",
			q_id);
		return -EINVAL;
	}

	if (msg_size != sizeof(*rq_info)) {
		nic_err(nic_io->dev_hdl, "Unexpect out buf size from user: %u, expect: %lu\n",
			msg_size, sizeof(*rq_info));
		return -EINVAL;
	}

	rq = &nic_io->rq[q_id];

	rq_info->q_id = q_id;

	rq_info->hw_pi = cpu_to_be16(*rq->rx.pi_virt_addr);
	rq_info->ci = sssnic_get_rq_local_ci(rq);

	rq_info->sw_pi = 0;

	rq_info->wqebb_size = rq->wq.wqebb_size;
	rq_info->q_depth = (u16)rq->wq.q_depth;

	rq_info->buf_len = nic_io->rx_buff_len;

	rq_info->slq_handle = rq;

	rq_info->ci_wqe_page_addr = ssshw_wq_get_first_wqe_page_addr(&rq->wq);
	rq_info->ci_cla_tbl_addr = rq->wq.wq_block_paddr;

	rq_info->msix_idx = rq->msix_entry_idx;
	rq_info->msix_vector = 0;

	return 0;
}

